github.com/dtroyer-salad/og2/v2@v2.0.0-20240412154159-c47231610877/registry/remote/auth/scope_test.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package auth 17 18 import ( 19 "context" 20 "reflect" 21 "testing" 22 23 "oras.land/oras-go/v2/registry" 24 ) 25 26 func TestScopeRepository(t *testing.T) { 27 tests := []struct { 28 name string 29 repository string 30 actions []string 31 want string 32 }{ 33 { 34 name: "empty repository", 35 actions: []string{ 36 "pull", 37 }, 38 }, 39 { 40 name: "nil actions", 41 repository: "foo", 42 }, 43 { 44 name: "empty actions", 45 repository: "foo", 46 actions: []string{}, 47 }, 48 { 49 name: "empty actions list", 50 repository: "foo", 51 actions: []string{}, 52 }, 53 { 54 name: "empty actions", 55 repository: "foo", 56 actions: []string{ 57 "", 58 }, 59 }, 60 { 61 name: "single action", 62 repository: "foo", 63 actions: []string{ 64 "pull", 65 }, 66 want: "repository:foo:pull", 67 }, 68 { 69 name: "multiple actions", 70 repository: "foo", 71 actions: []string{ 72 "pull", 73 "push", 74 }, 75 want: "repository:foo:pull,push", 76 }, 77 { 78 name: "unordered actions", 79 repository: "foo", 80 actions: []string{ 81 "push", 82 "pull", 83 }, 84 want: "repository:foo:pull,push", 85 }, 86 { 87 name: "duplicated actions", 88 repository: "foo", 89 actions: []string{ 90 "push", 91 "pull", 92 "pull", 93 "delete", 94 "push", 95 }, 96 want: "repository:foo:delete,pull,push", 97 }, 98 } 99 for _, tt := range tests { 100 t.Run(tt.name, func(t *testing.T) { 101 if got := ScopeRepository(tt.repository, tt.actions...); got != tt.want { 102 t.Errorf("ScopeRepository() = %v, want %v", got, tt.want) 103 } 104 }) 105 } 106 } 107 108 func TestWithScopeHints(t *testing.T) { 109 ctx := context.Background() 110 ref1, err := registry.ParseReference("registry.example.com/foo") 111 if err != nil { 112 t.Fatal("registry.ParseReference() error =", err) 113 } 114 ref2, err := registry.ParseReference("docker.io/foo") 115 if err != nil { 116 t.Fatal("registry.ParseReference() error =", err) 117 } 118 119 // with single scope 120 want1 := []string{ 121 "repository:foo:pull", 122 } 123 want2 := []string{ 124 "repository:foo:push", 125 } 126 ctx = AppendRepositoryScope(ctx, ref1, ActionPull) 127 ctx = AppendRepositoryScope(ctx, ref2, ActionPush) 128 if got := GetScopesForHost(ctx, ref1.Host()); !reflect.DeepEqual(got, want1) { 129 t.Errorf("GetScopesPerRegistry(WithScopeHints()) = %v, want %v", got, want1) 130 } 131 if got := GetScopesForHost(ctx, ref2.Host()); !reflect.DeepEqual(got, want2) { 132 t.Errorf("GetScopesPerRegistry(WithScopeHints()) = %v, want %v", got, want2) 133 } 134 135 // with duplicated scopes 136 scopes1 := []string{ 137 ActionDelete, 138 ActionDelete, 139 ActionPull, 140 } 141 want1 = []string{ 142 "repository:foo:delete,pull", 143 } 144 scopes2 := []string{ 145 ActionPush, 146 ActionPush, 147 ActionDelete, 148 } 149 want2 = []string{ 150 "repository:foo:delete,push", 151 } 152 ctx = AppendRepositoryScope(ctx, ref1, scopes1...) 153 ctx = AppendRepositoryScope(ctx, ref2, scopes2...) 154 if got := GetScopesForHost(ctx, ref1.Host()); !reflect.DeepEqual(got, want1) { 155 t.Errorf("GetScopesPerRegistry(WithScopeHints()) = %v, want %v", got, want1) 156 } 157 if got := GetScopesForHost(ctx, ref2.Host()); !reflect.DeepEqual(got, want2) { 158 t.Errorf("GetScopesPerRegistry(WithScopeHints()) = %v, want %v", got, want2) 159 } 160 161 // append empty scopes 162 ctx = AppendRepositoryScope(ctx, ref1) 163 ctx = AppendRepositoryScope(ctx, ref2) 164 if got := GetScopesForHost(ctx, ref1.Host()); !reflect.DeepEqual(got, want1) { 165 t.Errorf("GetScopesPerRegistry(WithScopeHints()) = %v, want %v", got, want1) 166 } 167 if got := GetScopesForHost(ctx, ref2.Host()); !reflect.DeepEqual(got, want2) { 168 t.Errorf("GetScopesPerRegistry(WithScopeHints()) = %v, want %v", got, want2) 169 } 170 } 171 172 func TestWithScopes(t *testing.T) { 173 ctx := context.Background() 174 175 // with single scope 176 want := []string{ 177 "repository:foo:pull", 178 } 179 ctx = WithScopes(ctx, want...) 180 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 181 t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) 182 } 183 184 // overwrite scopes 185 want = []string{ 186 "repository:bar:push", 187 } 188 ctx = WithScopes(ctx, want...) 189 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 190 t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) 191 } 192 193 // overwrite scopes with de-duplication 194 scopes := []string{ 195 "repository:hello-world:push", 196 "repository:alpine:delete", 197 "repository:hello-world:pull", 198 "repository:alpine:delete", 199 } 200 want = []string{ 201 "repository:alpine:delete", 202 "repository:hello-world:pull,push", 203 } 204 ctx = WithScopes(ctx, scopes...) 205 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 206 t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) 207 } 208 209 // clean scopes 210 want = nil 211 ctx = WithScopes(ctx, want...) 212 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 213 t.Errorf("GetScopes(WithScopes()) = %v, want %v", got, want) 214 } 215 } 216 217 func TestAppendScopes(t *testing.T) { 218 ctx := context.Background() 219 220 // append single scope 221 want := []string{ 222 "repository:foo:pull", 223 } 224 ctx = AppendScopes(ctx, want...) 225 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 226 t.Errorf("GetScopes(AppendScopes()) = %v, want %v", got, want) 227 } 228 229 // append scopes with de-duplication 230 scopes := []string{ 231 "repository:hello-world:push", 232 "repository:alpine:delete", 233 "repository:hello-world:pull", 234 "repository:alpine:delete", 235 } 236 want = []string{ 237 "repository:alpine:delete", 238 "repository:foo:pull", 239 "repository:hello-world:pull,push", 240 } 241 ctx = AppendScopes(ctx, scopes...) 242 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 243 t.Errorf("GetScopes(AppendScopes()) = %v, want %v", got, want) 244 } 245 246 // append empty scopes 247 ctx = AppendScopes(ctx) 248 if got := GetScopes(ctx); !reflect.DeepEqual(got, want) { 249 t.Errorf("GetScopes(AppendScopes()) = %v, want %v", got, want) 250 } 251 } 252 253 func TestWithScopesPerHost(t *testing.T) { 254 ctx := context.Background() 255 reg1 := "registry1.example.com" 256 reg2 := "registry2.example.com" 257 258 // with single scope 259 want1 := []string{ 260 "repository:foo:pull", 261 } 262 want2 := []string{ 263 "repository:foo:push", 264 } 265 ctx = WithScopesForHost(ctx, reg1, want1...) 266 ctx = WithScopesForHost(ctx, reg2, want2...) 267 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want1) { 268 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want1) 269 } 270 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want2) { 271 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want2) 272 } 273 274 // overwrite scopes 275 want1 = []string{ 276 "repository:bar:push", 277 } 278 want2 = []string{ 279 "repository:bar:pull", 280 } 281 ctx = WithScopesForHost(ctx, reg1, want1...) 282 ctx = WithScopesForHost(ctx, reg2, want2...) 283 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want1) { 284 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want1) 285 } 286 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want2) { 287 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want2) 288 } 289 290 // overwrite scopes with de-duplication 291 scopes1 := []string{ 292 "repository:hello-world:push", 293 "repository:alpine:delete", 294 "repository:hello-world:pull", 295 "repository:alpine:delete", 296 } 297 want1 = []string{ 298 "repository:alpine:delete", 299 "repository:hello-world:pull,push", 300 } 301 scopes2 := []string{ 302 "repository:goodbye-world:push", 303 "repository:nginx:delete", 304 "repository:goodbye-world:pull", 305 "repository:nginx:delete", 306 } 307 want2 = []string{ 308 "repository:goodbye-world:pull,push", 309 "repository:nginx:delete", 310 } 311 ctx = WithScopesForHost(ctx, reg1, scopes1...) 312 ctx = WithScopesForHost(ctx, reg2, scopes2...) 313 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want1) { 314 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want1) 315 } 316 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want2) { 317 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want2) 318 } 319 320 // clean scopes 321 var want []string 322 ctx = WithScopesForHost(ctx, reg1, want...) 323 ctx = WithScopesForHost(ctx, reg2, want...) 324 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want) { 325 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want) 326 } 327 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want) { 328 t.Errorf("GetScopesPerRegistry(WithScopesPerRegistry()) = %v, want %v", got, want) 329 } 330 } 331 332 func TestAppendScopesPerHost(t *testing.T) { 333 ctx := context.Background() 334 reg1 := "registry1.example.com" 335 reg2 := "registry2.example.com" 336 337 // with single scope 338 want1 := []string{ 339 "repository:foo:pull", 340 } 341 want2 := []string{ 342 "repository:foo:push", 343 } 344 ctx = AppendScopesForHost(ctx, reg1, want1...) 345 ctx = AppendScopesForHost(ctx, reg2, want2...) 346 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want1) { 347 t.Errorf("GetScopesPerRegistry(AppendScopesPerRegistry()) = %v, want %v", got, want1) 348 } 349 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want2) { 350 t.Errorf("GetScopesPerRegistry(AppendScopesPerRegistry()) = %v, want %v", got, want2) 351 } 352 353 // append scopes with de-duplication 354 scopes1 := []string{ 355 "repository:hello-world:push", 356 "repository:alpine:delete", 357 "repository:hello-world:pull", 358 "repository:alpine:delete", 359 } 360 want1 = []string{ 361 "repository:alpine:delete", 362 "repository:foo:pull", 363 "repository:hello-world:pull,push", 364 } 365 scopes2 := []string{ 366 "repository:goodbye-world:push", 367 "repository:nginx:delete", 368 "repository:goodbye-world:pull", 369 "repository:nginx:delete", 370 } 371 want2 = []string{ 372 "repository:foo:push", 373 "repository:goodbye-world:pull,push", 374 "repository:nginx:delete", 375 } 376 ctx = AppendScopesForHost(ctx, reg1, scopes1...) 377 ctx = AppendScopesForHost(ctx, reg2, scopes2...) 378 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want1) { 379 t.Errorf("GetScopesPerRegistry(AppendScopesPerRegistry()) = %v, want %v", got, want1) 380 } 381 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want2) { 382 t.Errorf("GetScopesPerRegistry(AppendScopesPerRegistry()) = %v, want %v", got, want2) 383 } 384 385 // append empty scopes 386 ctx = AppendScopesForHost(ctx, reg1) 387 ctx = AppendScopesForHost(ctx, reg2) 388 if got := GetScopesForHost(ctx, reg1); !reflect.DeepEqual(got, want1) { 389 t.Errorf("GetScopesPerRegistry(AppendScopesPerRegistry()) = %v, want %v", got, want1) 390 } 391 if got := GetScopesForHost(ctx, reg2); !reflect.DeepEqual(got, want2) { 392 t.Errorf("GetScopesPerRegistry(AppendScopesPerRegistry()) = %v, want %v", got, want2) 393 } 394 } 395 396 func TestCleanScopes(t *testing.T) { 397 tests := []struct { 398 name string 399 scopes []string 400 want []string 401 }{ 402 { 403 name: "nil scope", 404 }, 405 { 406 name: "empty scope", 407 scopes: []string{}, 408 }, 409 { 410 name: "single scope", 411 scopes: []string{ 412 "repository:foo:pull", 413 }, 414 want: []string{ 415 "repository:foo:pull", 416 }, 417 }, 418 { 419 name: "single scope with unordered actions", 420 scopes: []string{ 421 "repository:foo:push,pull,delete", 422 }, 423 want: []string{ 424 "repository:foo:delete,pull,push", 425 }, 426 }, 427 { 428 name: "single scope with duplicated actions", 429 scopes: []string{ 430 "repository:foo:push,pull,push,pull,push,push,pull", 431 }, 432 want: []string{ 433 "repository:foo:pull,push", 434 }, 435 }, 436 { 437 name: "single scope with wild cards", 438 scopes: []string{ 439 "repository:foo:pull,*,push", 440 }, 441 want: []string{ 442 "repository:foo:*", 443 }, 444 }, 445 { 446 name: "single scope with no actions", 447 scopes: []string{ 448 "repository:foo:,", 449 }, 450 want: nil, 451 }, 452 { 453 name: "multiple scopes", 454 scopes: []string{ 455 "repository:bar:push", 456 "repository:foo:pull", 457 }, 458 want: []string{ 459 "repository:bar:push", 460 "repository:foo:pull", 461 }, 462 }, 463 { 464 name: "multiple unordered scopes", 465 scopes: []string{ 466 "repository:foo:pull", 467 "repository:bar:push", 468 }, 469 want: []string{ 470 "repository:bar:push", 471 "repository:foo:pull", 472 }, 473 }, 474 { 475 name: "multiple scopes with duplicates", 476 scopes: []string{ 477 "repository:foo:pull", 478 "repository:bar:push", 479 "repository:foo:push", 480 "repository:bar:push,delete,pull", 481 "repository:bar:delete,pull", 482 "repository:foo:pull", 483 "registry:catalog:*", 484 "registry:catalog:pull", 485 }, 486 want: []string{ 487 "registry:catalog:*", 488 "repository:bar:delete,pull,push", 489 "repository:foo:pull,push", 490 }, 491 }, 492 { 493 name: "multiple scopes with no actions", 494 scopes: []string{ 495 "repository:foo:,", 496 "repository:bar:,", 497 }, 498 want: nil, 499 }, 500 { 501 name: "single unknown or invalid scope", 502 scopes: []string{ 503 "unknown", 504 }, 505 want: []string{ 506 "unknown", 507 }, 508 }, 509 { 510 name: "multiple unknown or invalid scopes", 511 scopes: []string{ 512 "repository:foo:pull", 513 "unknown", 514 "invalid:scope", 515 "no:actions:", 516 "repository:foo:push", 517 }, 518 want: []string{ 519 "invalid:scope", 520 "repository:foo:pull,push", 521 "unknown", 522 }, 523 }, 524 } 525 for _, tt := range tests { 526 t.Run(tt.name, func(t *testing.T) { 527 if got := CleanScopes(tt.scopes); !reflect.DeepEqual(got, tt.want) { 528 t.Errorf("CleanScopes() = %v, want %v", got, tt.want) 529 } 530 }) 531 } 532 } 533 534 func Test_cleanActions(t *testing.T) { 535 tests := []struct { 536 name string 537 actions []string 538 want []string 539 }{ 540 { 541 name: "nil action", 542 }, 543 { 544 name: "empty action", 545 actions: []string{}, 546 }, 547 { 548 name: "single action", 549 actions: []string{ 550 "pull", 551 }, 552 want: []string{ 553 "pull", 554 }, 555 }, 556 { 557 name: "single empty action", 558 actions: []string{ 559 "", 560 }, 561 }, 562 { 563 name: "multiple actions", 564 actions: []string{ 565 "pull", 566 "push", 567 }, 568 want: []string{ 569 "pull", 570 "push", 571 }, 572 }, 573 { 574 name: "multiple actions with empty action", 575 actions: []string{ 576 "pull", 577 "", 578 "push", 579 }, 580 want: []string{ 581 "pull", 582 "push", 583 }, 584 }, 585 { 586 name: "multiple actions with all empty action", 587 actions: []string{ 588 "", 589 "", 590 "", 591 }, 592 want: nil, 593 }, 594 { 595 name: "unordered actions", 596 actions: []string{ 597 "push", 598 "pull", 599 "delete", 600 }, 601 want: []string{ 602 "delete", 603 "pull", 604 "push", 605 }, 606 }, 607 { 608 name: "wildcard", 609 actions: []string{ 610 "*", 611 }, 612 want: []string{ 613 "*", 614 }, 615 }, 616 { 617 name: "wildcard at the begining", 618 actions: []string{ 619 "*", 620 "push", 621 "pull", 622 "delete", 623 }, 624 want: []string{ 625 "*", 626 }, 627 }, 628 { 629 name: "wildcard in the middle", 630 actions: []string{ 631 "push", 632 "pull", 633 "*", 634 "delete", 635 }, 636 want: []string{ 637 "*", 638 }, 639 }, 640 { 641 name: "wildcard at the end", 642 actions: []string{ 643 "push", 644 "pull", 645 "delete", 646 "*", 647 }, 648 want: []string{ 649 "*", 650 }, 651 }, 652 } 653 for _, tt := range tests { 654 t.Run(tt.name, func(t *testing.T) { 655 if got := cleanActions(tt.actions); !reflect.DeepEqual(got, tt.want) { 656 t.Errorf("cleanActions() = %v, want %v", got, tt.want) 657 } 658 }) 659 } 660 } 661 662 func Test_getAllScopesForHost(t *testing.T) { 663 host := "registry.example.com" 664 tests := []struct { 665 name string 666 scopes []string 667 globalScopes []string 668 want []string 669 }{ 670 { 671 name: "Empty per-host scopes", 672 scopes: []string{}, 673 globalScopes: []string{ 674 "repository:hello-world:push", 675 "repository:alpine:delete", 676 "repository:hello-world:pull", 677 "repository:alpine:delete", 678 }, 679 want: []string{ 680 "repository:alpine:delete", 681 "repository:hello-world:pull,push", 682 }, 683 }, 684 { 685 name: "Empty global scopes", 686 scopes: []string{ 687 "repository:hello-world:push", 688 "repository:alpine:delete", 689 "repository:hello-world:pull", 690 "repository:alpine:delete", 691 }, 692 globalScopes: []string{}, 693 want: []string{ 694 "repository:alpine:delete", 695 "repository:hello-world:pull,push", 696 }, 697 }, 698 { 699 name: "Per-host scopes + global scopes", 700 scopes: []string{ 701 "repository:hello-world:push", 702 "repository:alpine:delete", 703 "repository:hello-world:pull", 704 "repository:alpine:delete", 705 }, 706 globalScopes: []string{ 707 "repository:foo:pull", 708 "repository:hello-world:pull", 709 "repository:alpine:pull", 710 }, 711 want: []string{ 712 "repository:alpine:delete,pull", 713 "repository:foo:pull", 714 "repository:hello-world:pull,push", 715 }, 716 }, 717 } 718 for _, tt := range tests { 719 t.Run(tt.name, func(t *testing.T) { 720 ctx := context.Background() 721 ctx = WithScopesForHost(ctx, host, tt.scopes...) 722 ctx = WithScopes(ctx, tt.globalScopes...) 723 if got := GetAllScopesForHost(ctx, host); !reflect.DeepEqual(got, tt.want) { 724 t.Errorf("getAllScopesForHost() = %v, want %v", got, tt.want) 725 } 726 }) 727 } 728 }