-
Notifications
You must be signed in to change notification settings - Fork 38
/
validator.go
189 lines (156 loc) · 3.97 KB
/
validator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package eacl
import (
"bytes"
"errors"
"github.com/nspcc-dev/neofs-node/pkg/core/container"
"github.com/nspcc-dev/neofs-node/pkg/util/logger"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"go.uber.org/zap"
)
// Validator is a tool that calculates
// the action on a request according
// to the extended ACL rule table.
type Validator struct {
*cfg
}
// Option represents Validator option.
type Option func(*cfg)
type cfg struct {
logger *logger.Logger
storage Source
}
func defaultCfg() *cfg {
return &cfg{
logger: zap.L(),
}
}
// NewValidator creates and initializes a new Validator using options.
func NewValidator(opts ...Option) *Validator {
cfg := defaultCfg()
for i := range opts {
opts[i](cfg)
}
return &Validator{
cfg: cfg,
}
}
// CalculateAction calculates action on the request according
// to its information represented in ValidationUnit.
//
// The action is calculated according to the application of
// eACL table of rules to the request.
//
// If the eACL table is not available at the time of the call,
// eacl.ActionUnknown is returned.
//
// If no matching table entry is found, ActionAllow is returned.
func (v *Validator) CalculateAction(unit *ValidationUnit) eacl.Action {
var (
err error
table *eacl.Table
)
if unit.bearer != nil {
table = eacl.NewTableFromV2(unit.bearer.GetBody().GetEACL())
} else {
// get eACL table by container ID
table, err = v.storage.GetEACL(unit.cid)
if err != nil {
if errors.Is(err, container.ErrEACLNotFound) {
return eacl.ActionAllow
}
v.logger.Error("could not get eACL table",
zap.String("error", err.Error()),
)
return eacl.ActionUnknown
}
}
return tableAction(unit, table)
}
// calculates action on the request based on the eACL rules.
func tableAction(unit *ValidationUnit, table *eacl.Table) eacl.Action {
for _, record := range table.Records() {
// check type of operation
if record.Operation() != unit.op {
continue
}
// check target
if !targetMatches(unit, record) {
continue
}
// check headers
switch val := matchFilters(unit.hdrSrc, record.Filters()); {
case val < 0:
// headers of some type could not be composed => allow
return eacl.ActionAllow
case val == 0:
return record.Action()
}
}
return eacl.ActionAllow
}
// returns:
// - positive value if no matching header is found for at least one filter;
// - zero if at least one suitable header is found for all filters;
// - negative value if the headers of at least one filter cannot be obtained.
func matchFilters(hdrSrc TypedHeaderSource, filters []*eacl.Filter) int {
matched := 0
for _, filter := range filters {
headers, ok := hdrSrc.HeadersOfType(filter.From())
if !ok {
return -1
}
// get headers of filtering type
for _, header := range headers {
// prevent NPE
if header == nil {
continue
}
// check header name
if header.Key() != filter.Key() {
continue
}
// get match function
matchFn, ok := mMatchFns[filter.Matcher()]
if !ok {
continue
}
// check match
if !matchFn(header, filter) {
continue
}
// increment match counter
matched++
break
}
}
return len(filters) - matched
}
// returns true if one of ExtendedACLTarget has
// suitable target OR suitable public key.
func targetMatches(unit *ValidationUnit, record *eacl.Record) bool {
for _, target := range record.Targets() {
// check public key match
if pubs := target.BinaryKeys(); len(pubs) != 0 {
for _, key := range pubs {
if bytes.Equal(key, unit.key) {
return true
}
}
continue
}
// check target group match
if unit.role == target.Role() {
return true
}
}
return false
}
// Maps match type to corresponding function.
var mMatchFns = map[eacl.Match]func(Header, *eacl.Filter) bool{
eacl.MatchStringEqual: func(header Header, filter *eacl.Filter) bool {
return header.Value() == filter.Value()
},
eacl.MatchStringNotEqual: func(header Header, filter *eacl.Filter) bool {
return header.Value() != filter.Value()
},
}