forked from vanadium-archive/go.v23
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.go
247 lines (226 loc) · 8.6 KB
/
util.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package util
import (
"fmt"
"strings"
"unicode"
"v.io/v23/context"
"v.io/v23/conventions"
"v.io/v23/naming"
"v.io/v23/query/pattern"
"v.io/v23/security"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
"v.io/v23/verror"
)
// Encode escapes a component name for use in a Syncbase object name. In
// particular, it replaces bytes "%" and "/" with the "%" character followed by
// the byte's two-digit hex code. Clients using the client library need not
// escape names themselves; the client library does so on their behalf.
func Encode(s string) string {
return naming.EncodeAsNameElement(s)
}
// Decode is the inverse of Encode.
func Decode(s string) (string, error) {
res, ok := naming.DecodeFromNameElement(s)
if !ok {
return "", fmt.Errorf("failed to decode component name: %s", s)
}
return res, nil
}
// EncodeId encodes the given Id for use in a Syncbase object name.
func EncodeId(id wire.Id) string {
return Encode(id.String())
}
// DecodeId is the inverse of EncodeId.
func DecodeId(s string) (wire.Id, error) {
dec, err := Decode(s)
if err != nil {
return wire.Id{}, err
}
return wire.ParseId(dec)
}
// Currently we use \xff for perms index storage and \xfe as a component
// separator in storage engine keys. \xfc and \xfd are not used. In the future
// we might use \xfc followed by "c", "d", "e", or "f" as an order-preserving
// 2-byte encoding of our reserved bytes. Note that all valid UTF-8 byte
// sequences not containing the NUL character are allowed.
var reservedBytes = []string{"\x00", "\xfc", "\xfd", "\xfe", "\xff"}
func containsAnyOf(s string, needles []string) bool {
for _, v := range needles {
if strings.Contains(s, v) {
return true
}
}
return false
}
const (
// maxBlessingLen is the max allowed number of bytes in an Id blessing.
// TODO(ivanpi): Blessings are theoretically unbounded in length.
maxBlessingLen = 1024
// maxNameLen is the max allowed number of bytes in an Id name.
maxNameLen = 1024
)
// ValidateId returns nil iff the given Id is a valid database, collection, or
// syncgroup Id.
// TODO(ivanpi): Use verror.New instead of fmt.Errorf everywhere.
func ValidateId(id wire.Id) error {
if x := len([]byte(id.Blessing)); x == 0 {
return fmt.Errorf("Id blessing cannot be empty")
} else if x > maxBlessingLen {
return fmt.Errorf("Id blessing %q exceeds %d bytes", id.Blessing, maxBlessingLen)
}
if x := len([]byte(id.Name)); x == 0 {
return fmt.Errorf("Id name cannot be empty")
} else if x > maxNameLen {
return fmt.Errorf("Id name %q exceeds %d bytes", id.Name, maxNameLen)
}
if bp := security.BlessingPattern(id.Blessing); bp == security.NoExtension {
return fmt.Errorf("Id blessing %q cannot match any blessings, check blessing conventions", id.Blessing)
} else if !bp.IsValid() { // also checks for control characters
return fmt.Errorf("Id blessing %q is not a valid blessing pattern", id.Blessing)
}
if containsAnyOf(id.Blessing, reservedBytes) {
return fmt.Errorf("Id blessing %q contains a reserved byte (one of %q)", id.Blessing, reservedBytes)
}
if strings.IndexFunc(id.Name, unicode.IsControl) != -1 {
return fmt.Errorf("Id name %q contains a control character", id.Name)
}
if containsAnyOf(id.Name, reservedBytes) {
return fmt.Errorf("Id name %q contains a reserved byte (one of %q)", id.Name, reservedBytes)
}
return nil
}
// ValidateRowKey returns nil iff the given string is a valid row key.
func ValidateRowKey(s string) error {
if s == "" {
return fmt.Errorf("row key cannot be empty")
}
if strings.IndexFunc(s, unicode.IsControl) != -1 {
return fmt.Errorf("row key %q contains a control character", s)
}
if containsAnyOf(s, reservedBytes) {
return fmt.Errorf("row key %q contains a reserved byte (one of %q)", s, reservedBytes)
}
return nil
}
// ParseCollectionRowPair splits the "<encCxId>/<rowKey>" part of a Syncbase
// object name into the collection id and the row key or prefix.
func ParseCollectionRowPair(ctx *context.T, pattern string) (wire.Id, string, error) {
parts := strings.SplitN(pattern, "/", 2)
if len(parts) != 2 { // require both collection and row parts
return wire.Id{}, "", verror.New(verror.ErrBadArg, ctx, pattern)
}
collectionEnc, row := parts[0], parts[1]
collection, err := DecodeId(parts[0])
if err == nil {
err = ValidateId(collection)
}
if err != nil {
return wire.Id{}, "", verror.New(wire.ErrInvalidName, ctx, collectionEnc, err)
}
if row != "" {
if err := ValidateRowKey(row); err != nil {
return wire.Id{}, "", verror.New(wire.ErrInvalidName, ctx, row, err)
}
}
return collection, row, nil
}
// RowPrefixPattern returns a CollectionRowPattern matching a single key prefix
// in a single collection.
func RowPrefixPattern(cxId wire.Id, keyPrefix string) wire.CollectionRowPattern {
return wire.CollectionRowPattern{
CollectionBlessing: pattern.Escape(cxId.Blessing),
CollectionName: pattern.Escape(cxId.Name),
RowKey: pattern.Escape(keyPrefix) + "%",
}
}
// PrefixRangeStart returns the start of the row range for the given prefix.
func PrefixRangeStart(p string) string {
return p
}
// PrefixRangeLimit returns the limit of the row range for the given prefix.
func PrefixRangeLimit(p string) string {
// A string is a []byte, i.e. can be thought of as a base-256 number. The code
// below effectively adds 1 to this number, then chops off any trailing \x00
// bytes. If the input string consists entirely of \xff bytes, we return an
// empty string.
x := []byte(p)
for len(x) > 0 {
if x[len(x)-1] == 255 {
x = x[:len(x)-1] // chop off trailing \x00
} else {
x[len(x)-1] += 1 // add 1
break // no carry
}
}
return string(x)
}
// IsPrefix returns true if start and limit together represent a prefix range.
// If true, start is the represented prefix.
func IsPrefix(start, limit string) bool {
return PrefixRangeLimit(start) == limit
}
// AccessController provides access control for various syncbase objects.
type AccessController interface {
// SetPermissions replaces the current Permissions for an object.
// For detailed documentation, see Object.SetPermissions.
SetPermissions(ctx *context.T, perms access.Permissions, version string) error
// GetPermissions returns the current Permissions for an object.
// For detailed documentation, see Object.GetPermissions.
GetPermissions(ctx *context.T) (perms access.Permissions, version string, err error)
}
// AppAndUserPatternFromBlessings infers the app and user blessing pattern from
// the given set of blessing names.
// <idp>:o:<app>:<user> blessings are preferred, with a fallback to
// <idp>:u:<user> and unrestricted app. Returns an error and no-match patterns
// if the inferred pattern is ambiguous (multiple blessings for different apps
// or users are found), or if no blessings matching conventions are found.
// TODO(ivanpi): Allow caller to restrict format to app:user or user instead of
// automatic fallback?
func AppAndUserPatternFromBlessings(blessings ...string) (app, user security.BlessingPattern, err error) {
pbs := conventions.ParseBlessingNames(blessings...)
found := false
// Find a blessing of the form app:user; ensure there is only one app:user pair.
for _, b := range pbs {
a, au := b.AppPattern(), b.AppUserPattern()
if a != security.NoExtension && au != security.NoExtension {
if found && (a != app || au != user) {
return security.NoExtension, security.NoExtension, NewErrFoundMultipleAppUserBlessings(nil, string(user), string(au))
}
app, user, found = a, au, true
}
}
if found {
return app, user, nil
}
// Fall back to a user blessing; ensure there is only one user.
for _, b := range pbs {
u := b.UserPattern()
if u != security.NoExtension {
if found && (u != user) {
return security.NoExtension, security.NoExtension, NewErrFoundMultipleUserBlessings(nil, string(user), string(u))
}
app, user, found = security.AllPrincipals, u, true
}
}
if found {
return app, user, nil
}
// No app:user or user blessings found.
return security.NoExtension, security.NoExtension, NewErrFoundNoConventionalBlessings(nil)
}
// FilterTags returns a copy of the provided perms, filtered to include only
// entries for tags allowed by the allowTags whitelist.
func FilterTags(perms access.Permissions, allowTags ...access.Tag) (filtered access.Permissions) {
filtered = access.Permissions{}
for _, allowTag := range allowTags {
if acl, ok := perms[string(allowTag)]; ok {
filtered[string(allowTag)] = acl
}
}
// Copy to make sure lists in ACLs don't share backing arrays.
return filtered.Copy()
}