1 package ociauth
2
3 import (
4 "math/bits"
5 "strings"
6
7 "cuelabs.dev/go/oci/ociregistry/internal/exp/slices"
8 )
9
10
11
12 type knownAction byte
13
14 const (
15 unknownAction knownAction = iota
16
17 pullAction
18 pushAction
19 numActions
20 )
21
22 const (
23
24 TypeRepository = "repository"
25 TypeRegistry = "registry"
26
27
28 ActionPull = "pull"
29 ActionPush = "push"
30 )
31
32 func (a knownAction) String() string {
33 switch a {
34 case pullAction:
35 return ActionPull
36 case pushAction:
37 return ActionPush
38 default:
39 return "unknown"
40 }
41 }
42
43
44
45 var CatalogScope = ResourceScope{
46 ResourceType: TypeRegistry,
47 Resource: "catalog",
48 Action: "*",
49 }
50
51
52
53
54
55 type ResourceScope struct {
56
57
58
59
60 ResourceType string
61
62
63
64 Resource string
65
66
67
68 Action string
69 }
70
71 func (rs1 ResourceScope) Equal(rs2 ResourceScope) bool {
72 return rs1.Compare(rs2) == 0
73 }
74
75
76
77
78
79
80 func (rs1 ResourceScope) Compare(rs2 ResourceScope) int {
81 if c := strings.Compare(rs1.ResourceType, rs2.ResourceType); c != 0 {
82 return c
83 }
84 if c := strings.Compare(rs1.Resource, rs2.Resource); c != 0 {
85 return c
86 }
87 return strings.Compare(rs1.Action, rs2.Action)
88 }
89
90 func (rs ResourceScope) isKnown() bool {
91 switch rs.ResourceType {
92 case TypeRepository:
93 return parseKnownAction(rs.Action) != unknownAction
94 case TypeRegistry:
95 return rs == CatalogScope
96 }
97 return false
98 }
99
100
101
102 type Scope struct {
103
104
105
106 original string
107
108
109
110 unlimited bool
111
112
113
114
115 repositories []string
116
117
118
119
120
121
122 actions []byte
123
124
125
126
127 others []ResourceScope
128 }
129
130
131
132
133
134
135
136 func ParseScope(s string) Scope {
137 fields := strings.Fields(s)
138 rscopes := make([]ResourceScope, 0, len(fields))
139 for _, f := range fields {
140 parts := strings.Split(f, ":")
141 if len(parts) != 3 {
142 rscopes = append(rscopes, ResourceScope{
143 ResourceType: f,
144 })
145 continue
146 }
147 for _, action := range strings.Split(parts[2], ",") {
148 rscopes = append(rscopes, ResourceScope{
149 ResourceType: parts[0],
150 Resource: parts[1],
151 Action: action,
152 })
153 }
154 }
155 scope := NewScope(rscopes...)
156 scope.original = s
157 return scope
158 }
159
160
161 func NewScope(rss ...ResourceScope) Scope {
162
163 slices.SortFunc(rss, ResourceScope.Compare)
164 rss = slices.Compact(rss)
165 var s Scope
166 for _, rs := range rss {
167 if !rs.isKnown() {
168 s.others = append(s.others, rs)
169 continue
170 }
171 if rs.ResourceType == TypeRegistry {
172
173 s.repositories = append(s.repositories, "")
174 s.actions = append(s.actions, 1<<pullAction)
175 continue
176 }
177 actionMask := byte(1 << parseKnownAction(rs.Action))
178 if i := len(s.repositories); i > 0 && s.repositories[i-1] == rs.Resource {
179 s.actions[i-1] |= actionMask
180 } else {
181 s.repositories = append(s.repositories, rs.Resource)
182 s.actions = append(s.actions, actionMask)
183 }
184 }
185 slices.SortFunc(s.others, ResourceScope.Compare)
186 s.others = slices.Compact(s.others)
187 return s
188 }
189
190
191
192 func (s Scope) Len() int {
193 if s.IsUnlimited() {
194 panic("Len called on unlimited scope")
195 }
196 n := len(s.others)
197 for _, b := range s.actions {
198 n += bits.OnesCount8(b)
199 }
200 return n
201 }
202
203
204
205
206
207 func UnlimitedScope() Scope {
208 return Scope{
209 unlimited: true,
210 }
211 }
212
213
214 func (s Scope) IsUnlimited() bool {
215 return s.unlimited
216 }
217
218
219 func (s Scope) IsEmpty() bool {
220 return len(s.repositories) == 0 &&
221 len(s.others) == 0 &&
222 !s.unlimited
223 }
224
225
226
227
228
229
230 func (s Scope) Iter() func(yield func(ResourceScope) bool) {
231 return func(yield0 func(ResourceScope) bool) {
232 if s.unlimited {
233 return
234 }
235 others := s.others
236 yield := func(scope ResourceScope) bool {
237
238
239
240 for len(others) > 0 && others[0].Compare(scope) < 0 {
241 if !yield0(others[0]) {
242 return false
243 }
244 others = others[1:]
245 }
246 return yield0(scope)
247 }
248 for i, repo := range s.repositories {
249 if repo == "" {
250 if !yield(CatalogScope) {
251 return
252 }
253 continue
254 }
255 acts := s.actions[i]
256 for k := knownAction(0); k < numActions; k++ {
257 if acts&(1<<k) == 0 {
258 continue
259 }
260 rscope := ResourceScope{
261 ResourceType: TypeRepository,
262 Resource: repo,
263 Action: k.String(),
264 }
265 if !yield(rscope) {
266 return
267 }
268 }
269 }
270
271 for _, rscope := range others {
272 if !yield0(rscope) {
273 return
274 }
275 }
276 }
277 }
278
279
280
281
282 func (s1 Scope) Union(s2 Scope) Scope {
283 if s1.IsUnlimited() || s2.IsUnlimited() {
284 return UnlimitedScope()
285 }
286
287 if s2.IsEmpty() || s1.Equal(s2) {
288 return s1
289 }
290 r := Scope{
291 repositories: make([]string, 0, len(s1.repositories)+len(s2.repositories)),
292 actions: make([]byte, 0, len(s1.repositories)+len(s2.repositories)),
293 others: make([]ResourceScope, 0, len(s1.others)+len(s2.others)),
294 }
295 i1, i2 := 0, 0
296 for i1 < len(s1.repositories) && i2 < len(s2.repositories) {
297 repo1, repo2 := s1.repositories[i1], s2.repositories[i2]
298
299 switch strings.Compare(repo1, repo2) {
300 case 0:
301 r.repositories = append(r.repositories, repo1)
302 r.actions = append(r.actions, s1.actions[i1]|s2.actions[i2])
303 i1++
304 i2++
305 case -1:
306 r.repositories = append(r.repositories, s1.repositories[i1])
307 r.actions = append(r.actions, s1.actions[i1])
308 i1++
309 case 1:
310 r.repositories = append(r.repositories, s2.repositories[i2])
311 r.actions = append(r.actions, s2.actions[i2])
312 i2++
313 default:
314 panic("unreachable")
315 }
316 }
317 switch {
318 case i1 < len(s1.repositories):
319 r.repositories = append(r.repositories, s1.repositories[i1:]...)
320 r.actions = append(r.actions, s1.actions[i1:]...)
321 case i2 < len(s2.repositories):
322 r.repositories = append(r.repositories, s2.repositories[i2:]...)
323 r.actions = append(r.actions, s2.actions[i2:]...)
324 }
325 i1, i2 = 0, 0
326 for i1 < len(s1.others) && i2 < len(s2.others) {
327 a1, a2 := s1.others[i1], s2.others[i2]
328 switch a1.Compare(a2) {
329 case 0:
330 r.others = append(r.others, a1)
331 i1++
332 i2++
333 case -1:
334 r.others = append(r.others, a1)
335 i1++
336 case 1:
337 r.others = append(r.others, a2)
338 i2++
339 }
340 }
341 switch {
342 case i1 < len(s1.others):
343 r.others = append(r.others, s1.others[i1:]...)
344 case i2 < len(s2.others):
345 r.others = append(r.others, s2.others[i2:]...)
346 }
347 if r.Equal(s1) {
348
349 return s1
350 }
351 return r
352 }
353
354 func (s Scope) Holds(r ResourceScope) bool {
355 if s.IsUnlimited() {
356 return true
357 }
358 if r == CatalogScope {
359 _, ok := slices.BinarySearch(s.repositories, "")
360 return ok
361 }
362 if r.ResourceType == TypeRepository {
363 if action := parseKnownAction(r.Action); action != unknownAction {
364
365 i, ok := slices.BinarySearch(s.repositories, r.Resource)
366 if !ok {
367 return false
368 }
369 return s.actions[i]&(1<<action) != 0
370 }
371 }
372
373
374
375 _, ok := slices.BinarySearchFunc(s.others, r, ResourceScope.Compare)
376 return ok
377 }
378
379
380 func (s1 Scope) Contains(s2 Scope) bool {
381 if s1.IsUnlimited() {
382 return true
383 }
384 if s2.IsUnlimited() {
385 return false
386 }
387 i1 := 0
388 outer1:
389 for i2, repo2 := range s2.repositories {
390 for i1 < len(s1.repositories) {
391 switch repo1 := s1.repositories[i1]; strings.Compare(repo1, repo2) {
392 case 1:
393
394 return false
395 case 0:
396 if (s1.actions[i1] & s2.actions[i2]) != s2.actions[i2] {
397
398 return false
399 }
400 i1++
401 continue outer1
402 case -1:
403 i1++
404
405 }
406 }
407
408 return false
409 }
410 i1 = 0
411 outer2:
412 for _, sc2 := range s2.others {
413 for i1 < len(s1.others) {
414 sc1 := s1.others[i1]
415 switch sc1.Compare(sc2) {
416 case 1:
417 return false
418 case 0:
419 i1++
420 continue outer2
421 case -1:
422 i1++
423 }
424 }
425 return false
426 }
427 return true
428 }
429
430 func (s1 Scope) Equal(s2 Scope) bool {
431 return s1.IsUnlimited() == s2.IsUnlimited() &&
432 slices.Equal(s1.repositories, s2.repositories) &&
433 slices.Equal(s1.actions, s2.actions) &&
434 slices.Equal(s1.others, s2.others)
435 }
436
437
438
439
440
441 func (s Scope) Canonical() Scope {
442 s.original = ""
443 return s
444 }
445
446
447
448 func (s Scope) String() string {
449 if s.IsUnlimited() {
450
451
452
453 return "*"
454 }
455 if s.original != "" || s.IsEmpty() {
456 return s.original
457 }
458 var buf strings.Builder
459 var prev ResourceScope
460
461 s.Iter()(func(s ResourceScope) bool {
462 prev0 := prev
463 prev = s
464 if s.ResourceType == TypeRepository && prev0.ResourceType == TypeRepository && s.Resource == prev0.Resource {
465 buf.WriteByte(',')
466 buf.WriteString(s.Action)
467 return true
468 }
469 if buf.Len() > 0 {
470 buf.WriteByte(' ')
471 }
472 buf.WriteString(s.ResourceType)
473 if s.Resource != "" || s.Action != "" {
474 buf.WriteByte(':')
475 buf.WriteString(s.Resource)
476 buf.WriteByte(':')
477 buf.WriteString(s.Action)
478 }
479 return true
480 })
481 return buf.String()
482 }
483
484 func parseKnownAction(s string) knownAction {
485 switch s {
486 case ActionPull:
487 return pullAction
488 case ActionPush:
489 return pushAction
490 default:
491 return unknownAction
492 }
493 }
494
View as plain text