github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/registry/remote/auth/client_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 "encoding/base64" 21 "errors" 22 "fmt" 23 "net/http" 24 "net/http/httptest" 25 "net/url" 26 "reflect" 27 "strings" 28 "sync/atomic" 29 "testing" 30 31 "github.com/opcr-io/oras-go/v2/registry/remote/errcode" 32 ) 33 34 func TestClient_SetUserAgent(t *testing.T) { 35 wantUserAgent := "test agent" 36 var requestCount, wantRequestCount int64 37 var successCount, wantSuccessCount int64 38 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 39 atomic.AddInt64(&requestCount, 1) 40 if r.Method != http.MethodGet || r.URL.Path != "/" { 41 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 42 w.WriteHeader(http.StatusNotFound) 43 return 44 } 45 if userAgent := r.UserAgent(); userAgent != wantUserAgent { 46 t.Errorf("unexpected User-Agent: %v, want %v", userAgent, wantUserAgent) 47 return 48 } 49 atomic.AddInt64(&successCount, 1) 50 })) 51 defer ts.Close() 52 53 var client Client 54 client.SetUserAgent(wantUserAgent) 55 56 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 57 if err != nil { 58 t.Fatalf("failed to create test request: %v", err) 59 } 60 resp, err := client.Do(req) 61 if err != nil { 62 t.Fatalf("Client.Do() error = %v", err) 63 } 64 if resp.StatusCode != http.StatusOK { 65 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 66 } 67 if wantRequestCount++; requestCount != wantRequestCount { 68 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 69 } 70 if wantSuccessCount++; successCount != wantSuccessCount { 71 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 72 } 73 } 74 75 func TestClient_Do_Basic_Auth(t *testing.T) { 76 username := "test_user" 77 password := "test_password" 78 var requestCount, wantRequestCount int64 79 var successCount, wantSuccessCount int64 80 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 81 atomic.AddInt64(&requestCount, 1) 82 if r.Method != http.MethodGet || r.URL.Path != "/" { 83 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 84 w.WriteHeader(http.StatusNotFound) 85 return 86 } 87 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 88 if auth := r.Header.Get("Authorization"); auth != header { 89 w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) 90 w.WriteHeader(http.StatusUnauthorized) 91 return 92 } 93 atomic.AddInt64(&successCount, 1) 94 })) 95 defer ts.Close() 96 uri, err := url.Parse(ts.URL) 97 if err != nil { 98 t.Fatalf("invalid test http server: %v", err) 99 } 100 101 client := &Client{ 102 Credential: func(ctx context.Context, reg string) (Credential, error) { 103 if reg != uri.Host { 104 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 105 t.Error(err) 106 return EmptyCredential, err 107 } 108 return Credential{ 109 Username: username, 110 Password: password, 111 }, nil 112 }, 113 } 114 115 // first request 116 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 117 if err != nil { 118 t.Fatalf("failed to create test request: %v", err) 119 } 120 resp, err := client.Do(req) 121 if err != nil { 122 t.Fatalf("Client.Do() error = %v", err) 123 } 124 if resp.StatusCode != http.StatusOK { 125 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 126 } 127 if wantRequestCount += 2; requestCount != wantRequestCount { 128 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 129 } 130 if wantSuccessCount++; successCount != wantSuccessCount { 131 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 132 } 133 134 // credential change 135 username = "test_user2" 136 password = "test_password2" 137 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 138 if err != nil { 139 t.Fatalf("failed to create test request: %v", err) 140 } 141 resp, err = client.Do(req) 142 if err != nil { 143 t.Fatalf("Client.Do() error = %v", err) 144 } 145 if resp.StatusCode != http.StatusOK { 146 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 147 } 148 if wantRequestCount += 2; requestCount != wantRequestCount { 149 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 150 } 151 if wantSuccessCount++; successCount != wantSuccessCount { 152 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 153 } 154 } 155 156 func TestClient_Do_Basic_Auth_Cached(t *testing.T) { 157 username := "test_user" 158 password := "test_password" 159 var requestCount, wantRequestCount int64 160 var successCount, wantSuccessCount int64 161 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 162 atomic.AddInt64(&requestCount, 1) 163 if r.Method != http.MethodGet || r.URL.Path != "/" { 164 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 165 w.WriteHeader(http.StatusNotFound) 166 return 167 } 168 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 169 if auth := r.Header.Get("Authorization"); auth != header { 170 w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) 171 w.WriteHeader(http.StatusUnauthorized) 172 return 173 } 174 atomic.AddInt64(&successCount, 1) 175 })) 176 defer ts.Close() 177 uri, err := url.Parse(ts.URL) 178 if err != nil { 179 t.Fatalf("invalid test http server: %v", err) 180 } 181 182 client := &Client{ 183 Credential: func(ctx context.Context, reg string) (Credential, error) { 184 if reg != uri.Host { 185 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 186 t.Error(err) 187 return EmptyCredential, err 188 } 189 return Credential{ 190 Username: username, 191 Password: password, 192 }, nil 193 }, 194 Cache: NewCache(), 195 } 196 197 // first request 198 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 199 if err != nil { 200 t.Fatalf("failed to create test request: %v", err) 201 } 202 resp, err := client.Do(req) 203 if err != nil { 204 t.Fatalf("Client.Do() error = %v", err) 205 } 206 if resp.StatusCode != http.StatusOK { 207 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 208 } 209 if wantRequestCount += 2; requestCount != wantRequestCount { 210 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 211 } 212 if wantSuccessCount++; successCount != wantSuccessCount { 213 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 214 } 215 216 // repeated request 217 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 218 if err != nil { 219 t.Fatalf("failed to create test request: %v", err) 220 } 221 resp, err = client.Do(req) 222 if err != nil { 223 t.Fatalf("Client.Do() error = %v", err) 224 } 225 if resp.StatusCode != http.StatusOK { 226 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 227 } 228 if wantRequestCount++; requestCount != wantRequestCount { 229 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 230 } 231 if wantSuccessCount++; successCount != wantSuccessCount { 232 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 233 } 234 235 // credential change 236 username = "test_user2" 237 password = "test_password2" 238 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 239 if err != nil { 240 t.Fatalf("failed to create test request: %v", err) 241 } 242 resp, err = client.Do(req) 243 if err != nil { 244 t.Fatalf("Client.Do() error = %v", err) 245 } 246 if resp.StatusCode != http.StatusOK { 247 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 248 } 249 if wantRequestCount += 2; requestCount != wantRequestCount { 250 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 251 } 252 if wantSuccessCount++; successCount != wantSuccessCount { 253 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 254 } 255 } 256 257 func TestClient_Do_Bearer_AccessToken(t *testing.T) { 258 accessToken := "test/access/token" 259 var requestCount, wantRequestCount int64 260 var successCount, wantSuccessCount int64 261 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 262 t.Error("unexecuted attempt of authorization service") 263 w.WriteHeader(http.StatusUnauthorized) 264 })) 265 defer as.Close() 266 var service string 267 scope := "repository:test:pull,push" 268 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 269 atomic.AddInt64(&requestCount, 1) 270 if r.Method != http.MethodGet || r.URL.Path != "/" { 271 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 272 w.WriteHeader(http.StatusNotFound) 273 return 274 } 275 header := "Bearer " + accessToken 276 if auth := r.Header.Get("Authorization"); auth != header { 277 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) 278 w.Header().Set("Www-Authenticate", challenge) 279 w.WriteHeader(http.StatusUnauthorized) 280 return 281 } 282 atomic.AddInt64(&successCount, 1) 283 })) 284 defer ts.Close() 285 uri, err := url.Parse(ts.URL) 286 if err != nil { 287 t.Fatalf("invalid test http server: %v", err) 288 } 289 service = uri.Host 290 291 client := &Client{ 292 Credential: func(ctx context.Context, reg string) (Credential, error) { 293 if reg != uri.Host { 294 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 295 t.Error(err) 296 return EmptyCredential, err 297 } 298 return Credential{ 299 AccessToken: accessToken, 300 }, nil 301 }, 302 } 303 304 // first request 305 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 306 if err != nil { 307 t.Fatalf("failed to create test request: %v", err) 308 } 309 resp, err := client.Do(req) 310 if err != nil { 311 t.Fatalf("Client.Do() error = %v", err) 312 } 313 if resp.StatusCode != http.StatusOK { 314 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 315 } 316 if wantRequestCount += 2; requestCount != wantRequestCount { 317 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 318 } 319 if wantSuccessCount++; successCount != wantSuccessCount { 320 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 321 } 322 323 // credential change 324 accessToken = "test/access/token/2" 325 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 326 if err != nil { 327 t.Fatalf("failed to create test request: %v", err) 328 } 329 resp, err = client.Do(req) 330 if err != nil { 331 t.Fatalf("Client.Do() error = %v", err) 332 } 333 if resp.StatusCode != http.StatusOK { 334 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 335 } 336 if wantRequestCount += 2; requestCount != wantRequestCount { 337 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 338 } 339 if wantSuccessCount++; successCount != wantSuccessCount { 340 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 341 } 342 } 343 344 func TestClient_Do_Bearer_AccessToken_Cached(t *testing.T) { 345 accessToken := "test/access/token" 346 var requestCount, wantRequestCount int64 347 var successCount, wantSuccessCount int64 348 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 349 t.Error("unexecuted attempt of authorization service") 350 w.WriteHeader(http.StatusUnauthorized) 351 })) 352 defer as.Close() 353 var service string 354 scope := "repository:test:pull,push" 355 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 356 atomic.AddInt64(&requestCount, 1) 357 if r.Method != http.MethodGet || r.URL.Path != "/" { 358 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 359 w.WriteHeader(http.StatusNotFound) 360 return 361 } 362 header := "Bearer " + accessToken 363 if auth := r.Header.Get("Authorization"); auth != header { 364 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) 365 w.Header().Set("Www-Authenticate", challenge) 366 w.WriteHeader(http.StatusUnauthorized) 367 return 368 } 369 atomic.AddInt64(&successCount, 1) 370 })) 371 defer ts.Close() 372 uri, err := url.Parse(ts.URL) 373 if err != nil { 374 t.Fatalf("invalid test http server: %v", err) 375 } 376 service = uri.Host 377 378 client := &Client{ 379 Credential: func(ctx context.Context, reg string) (Credential, error) { 380 if reg != uri.Host { 381 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 382 t.Error(err) 383 return EmptyCredential, err 384 } 385 return Credential{ 386 AccessToken: accessToken, 387 }, nil 388 }, 389 Cache: NewCache(), 390 } 391 392 // first request 393 ctx := WithScopes(context.Background(), scope) 394 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 395 if err != nil { 396 t.Fatalf("failed to create test request: %v", err) 397 } 398 resp, err := client.Do(req) 399 if err != nil { 400 t.Fatalf("Client.Do() error = %v", err) 401 } 402 if resp.StatusCode != http.StatusOK { 403 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 404 } 405 if wantRequestCount += 2; requestCount != wantRequestCount { 406 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 407 } 408 if wantSuccessCount++; successCount != wantSuccessCount { 409 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 410 } 411 412 // repeated request 413 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 414 if err != nil { 415 t.Fatalf("failed to create test request: %v", err) 416 } 417 resp, err = client.Do(req) 418 if err != nil { 419 t.Fatalf("Client.Do() error = %v", err) 420 } 421 if resp.StatusCode != http.StatusOK { 422 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 423 } 424 if wantRequestCount++; requestCount != wantRequestCount { 425 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 426 } 427 if wantSuccessCount++; successCount != wantSuccessCount { 428 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 429 } 430 431 // credential change 432 accessToken = "test/access/token/2" 433 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 434 if err != nil { 435 t.Fatalf("failed to create test request: %v", err) 436 } 437 resp, err = client.Do(req) 438 if err != nil { 439 t.Fatalf("Client.Do() error = %v", err) 440 } 441 if resp.StatusCode != http.StatusOK { 442 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 443 } 444 if wantRequestCount += 2; requestCount != wantRequestCount { 445 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 446 } 447 if wantSuccessCount++; successCount != wantSuccessCount { 448 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 449 } 450 } 451 452 func TestClient_Do_Bearer_Auth(t *testing.T) { 453 username := "test_user" 454 password := "test_password" 455 accessToken := "test/access/token" 456 var requestCount, wantRequestCount int64 457 var successCount, wantSuccessCount int64 458 var authCount, wantAuthCount int64 459 var service string 460 scopes := []string{ 461 "repository:dst:pull,push", 462 "repository:src:pull", 463 } 464 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 465 if r.Method != http.MethodGet || r.URL.Path != "/" { 466 t.Error("unexecuted attempt of authorization service") 467 w.WriteHeader(http.StatusUnauthorized) 468 return 469 } 470 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 471 if auth := r.Header.Get("Authorization"); auth != header { 472 t.Errorf("unexpected auth: got %s, want %s", auth, header) 473 w.WriteHeader(http.StatusUnauthorized) 474 return 475 } 476 if got := r.URL.Query().Get("service"); got != service { 477 t.Errorf("unexpected service: got %s, want %s", got, service) 478 w.WriteHeader(http.StatusUnauthorized) 479 return 480 } 481 if got := r.URL.Query()["scope"]; !reflect.DeepEqual(got, scopes) { 482 t.Errorf("unexpected scope: got %s, want %s", got, scopes) 483 w.WriteHeader(http.StatusUnauthorized) 484 return 485 } 486 487 atomic.AddInt64(&authCount, 1) 488 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 489 t.Errorf("failed to write %q: %v", r.URL, err) 490 } 491 })) 492 defer as.Close() 493 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 494 atomic.AddInt64(&requestCount, 1) 495 if r.Method != http.MethodGet || r.URL.Path != "/" { 496 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 497 w.WriteHeader(http.StatusNotFound) 498 return 499 } 500 header := "Bearer " + accessToken 501 if auth := r.Header.Get("Authorization"); auth != header { 502 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 503 w.Header().Set("Www-Authenticate", challenge) 504 w.WriteHeader(http.StatusUnauthorized) 505 return 506 } 507 atomic.AddInt64(&successCount, 1) 508 })) 509 defer ts.Close() 510 uri, err := url.Parse(ts.URL) 511 if err != nil { 512 t.Fatalf("invalid test http server: %v", err) 513 } 514 service = uri.Host 515 516 client := &Client{ 517 Credential: func(ctx context.Context, reg string) (Credential, error) { 518 if reg != uri.Host { 519 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 520 t.Error(err) 521 return EmptyCredential, err 522 } 523 return Credential{ 524 Username: username, 525 Password: password, 526 }, nil 527 }, 528 } 529 530 // first request 531 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 532 if err != nil { 533 t.Fatalf("failed to create test request: %v", err) 534 } 535 resp, err := client.Do(req) 536 if err != nil { 537 t.Fatalf("Client.Do() error = %v", err) 538 } 539 if resp.StatusCode != http.StatusOK { 540 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 541 } 542 if wantRequestCount += 2; requestCount != wantRequestCount { 543 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 544 } 545 if wantSuccessCount++; successCount != wantSuccessCount { 546 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 547 } 548 if wantAuthCount++; authCount != wantAuthCount { 549 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 550 } 551 552 // credential change 553 username = "test_user2" 554 password = "test_password2" 555 accessToken = "test/access/token/2" 556 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 557 if err != nil { 558 t.Fatalf("failed to create test request: %v", err) 559 } 560 resp, err = client.Do(req) 561 if err != nil { 562 t.Fatalf("Client.Do() error = %v", err) 563 } 564 if resp.StatusCode != http.StatusOK { 565 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 566 } 567 if wantRequestCount += 2; requestCount != wantRequestCount { 568 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 569 } 570 if wantSuccessCount++; successCount != wantSuccessCount { 571 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 572 } 573 if wantAuthCount++; authCount != wantAuthCount { 574 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 575 } 576 } 577 578 func TestClient_Do_Bearer_Auth_Cached(t *testing.T) { 579 username := "test_user" 580 password := "test_password" 581 accessToken := "test/access/token" 582 var requestCount, wantRequestCount int64 583 var successCount, wantSuccessCount int64 584 var authCount, wantAuthCount int64 585 var service string 586 scopes := []string{ 587 "repository:dst:pull,push", 588 "repository:src:pull", 589 } 590 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 591 if r.Method != http.MethodGet || r.URL.Path != "/" { 592 t.Error("unexecuted attempt of authorization service") 593 w.WriteHeader(http.StatusUnauthorized) 594 return 595 } 596 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 597 if auth := r.Header.Get("Authorization"); auth != header { 598 t.Errorf("unexpected auth: got %s, want %s", auth, header) 599 w.WriteHeader(http.StatusUnauthorized) 600 return 601 } 602 if got := r.URL.Query().Get("service"); got != service { 603 t.Errorf("unexpected service: got %s, want %s", got, service) 604 w.WriteHeader(http.StatusUnauthorized) 605 return 606 } 607 if got := r.URL.Query()["scope"]; !reflect.DeepEqual(got, scopes) { 608 t.Errorf("unexpected scope: got %s, want %s", got, scopes) 609 w.WriteHeader(http.StatusUnauthorized) 610 return 611 } 612 613 atomic.AddInt64(&authCount, 1) 614 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 615 t.Errorf("failed to write %q: %v", r.URL, err) 616 } 617 })) 618 defer as.Close() 619 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 620 atomic.AddInt64(&requestCount, 1) 621 if r.Method != http.MethodGet || r.URL.Path != "/" { 622 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 623 w.WriteHeader(http.StatusNotFound) 624 return 625 } 626 header := "Bearer " + accessToken 627 if auth := r.Header.Get("Authorization"); auth != header { 628 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 629 w.Header().Set("Www-Authenticate", challenge) 630 w.WriteHeader(http.StatusUnauthorized) 631 return 632 } 633 atomic.AddInt64(&successCount, 1) 634 })) 635 defer ts.Close() 636 uri, err := url.Parse(ts.URL) 637 if err != nil { 638 t.Fatalf("invalid test http server: %v", err) 639 } 640 service = uri.Host 641 642 client := &Client{ 643 Credential: func(ctx context.Context, reg string) (Credential, error) { 644 if reg != uri.Host { 645 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 646 t.Error(err) 647 return EmptyCredential, err 648 } 649 return Credential{ 650 Username: username, 651 Password: password, 652 }, nil 653 }, 654 Cache: NewCache(), 655 } 656 657 // first request 658 ctx := WithScopes(context.Background(), scopes...) 659 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 660 if err != nil { 661 t.Fatalf("failed to create test request: %v", err) 662 } 663 resp, err := client.Do(req) 664 if err != nil { 665 t.Fatalf("Client.Do() error = %v", err) 666 } 667 if resp.StatusCode != http.StatusOK { 668 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 669 } 670 if wantRequestCount += 2; requestCount != wantRequestCount { 671 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 672 } 673 if wantSuccessCount++; successCount != wantSuccessCount { 674 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 675 } 676 if wantAuthCount++; authCount != wantAuthCount { 677 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 678 } 679 680 // repeated request 681 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 682 if err != nil { 683 t.Fatalf("failed to create test request: %v", err) 684 } 685 resp, err = client.Do(req) 686 if err != nil { 687 t.Fatalf("Client.Do() error = %v", err) 688 } 689 if resp.StatusCode != http.StatusOK { 690 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 691 } 692 if wantRequestCount++; requestCount != wantRequestCount { 693 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 694 } 695 if wantSuccessCount++; successCount != wantSuccessCount { 696 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 697 } 698 if authCount != wantAuthCount { 699 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 700 } 701 702 // credential change 703 username = "test_user2" 704 password = "test_password2" 705 accessToken = "test/access/token/2" 706 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 707 if err != nil { 708 t.Fatalf("failed to create test request: %v", err) 709 } 710 resp, err = client.Do(req) 711 if err != nil { 712 t.Fatalf("Client.Do() error = %v", err) 713 } 714 if resp.StatusCode != http.StatusOK { 715 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 716 } 717 if wantRequestCount += 2; requestCount != wantRequestCount { 718 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 719 } 720 if wantSuccessCount++; successCount != wantSuccessCount { 721 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 722 } 723 if wantAuthCount++; authCount != wantAuthCount { 724 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 725 } 726 } 727 728 func TestClient_Do_Bearer_OAuth2_Password(t *testing.T) { 729 username := "test_user" 730 password := "test_password" 731 accessToken := "test/access/token" 732 var requestCount, wantRequestCount int64 733 var successCount, wantSuccessCount int64 734 var authCount, wantAuthCount int64 735 var service string 736 scopes := []string{ 737 "repository:dst:pull,push", 738 "repository:src:pull", 739 } 740 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 741 if r.Method != http.MethodPost || r.URL.Path != "/" { 742 t.Error("unexecuted attempt of authorization service") 743 w.WriteHeader(http.StatusUnauthorized) 744 return 745 } 746 if err := r.ParseForm(); err != nil { 747 t.Errorf("failed to parse form: %v", err) 748 w.WriteHeader(http.StatusUnauthorized) 749 return 750 } 751 if got := r.PostForm.Get("grant_type"); got != "password" { 752 t.Errorf("unexpected grant type: %v, want %v", got, "password") 753 w.WriteHeader(http.StatusUnauthorized) 754 return 755 } 756 if got := r.PostForm.Get("service"); got != service { 757 t.Errorf("unexpected service: %v, want %v", got, service) 758 w.WriteHeader(http.StatusUnauthorized) 759 return 760 } 761 if got := r.PostForm.Get("client_id"); got != defaultClientID { 762 t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) 763 w.WriteHeader(http.StatusUnauthorized) 764 return 765 } 766 scope := strings.Join(scopes, " ") 767 if got := r.PostForm.Get("scope"); got != scope { 768 t.Errorf("unexpected scope: %v, want %v", got, scope) 769 w.WriteHeader(http.StatusUnauthorized) 770 return 771 } 772 if got := r.PostForm.Get("username"); got != username { 773 t.Errorf("unexpected username: %v, want %v", got, username) 774 w.WriteHeader(http.StatusUnauthorized) 775 return 776 } 777 if got := r.PostForm.Get("password"); got != password { 778 t.Errorf("unexpected password: %v, want %v", got, password) 779 w.WriteHeader(http.StatusUnauthorized) 780 return 781 } 782 783 atomic.AddInt64(&authCount, 1) 784 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 785 t.Errorf("failed to write %q: %v", r.URL, err) 786 } 787 })) 788 defer as.Close() 789 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 790 atomic.AddInt64(&requestCount, 1) 791 if r.Method != http.MethodGet || r.URL.Path != "/" { 792 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 793 w.WriteHeader(http.StatusNotFound) 794 return 795 } 796 header := "Bearer " + accessToken 797 if auth := r.Header.Get("Authorization"); auth != header { 798 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 799 w.Header().Set("Www-Authenticate", challenge) 800 w.WriteHeader(http.StatusUnauthorized) 801 return 802 } 803 atomic.AddInt64(&successCount, 1) 804 })) 805 defer ts.Close() 806 uri, err := url.Parse(ts.URL) 807 if err != nil { 808 t.Fatalf("invalid test http server: %v", err) 809 } 810 service = uri.Host 811 812 client := &Client{ 813 Credential: func(ctx context.Context, reg string) (Credential, error) { 814 if reg != uri.Host { 815 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 816 t.Error(err) 817 return EmptyCredential, err 818 } 819 return Credential{ 820 Username: username, 821 Password: password, 822 }, nil 823 }, 824 ForceAttemptOAuth2: true, 825 } 826 827 // first request 828 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 829 if err != nil { 830 t.Fatalf("failed to create test request: %v", err) 831 } 832 resp, err := client.Do(req) 833 if err != nil { 834 t.Fatalf("Client.Do() error = %v", err) 835 } 836 if resp.StatusCode != http.StatusOK { 837 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 838 } 839 if wantRequestCount += 2; requestCount != wantRequestCount { 840 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 841 } 842 if wantSuccessCount++; successCount != wantSuccessCount { 843 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 844 } 845 if wantAuthCount++; authCount != wantAuthCount { 846 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 847 } 848 849 // credential change 850 username = "test_user2" 851 password = "test_password2" 852 accessToken = "test/access/token/2" 853 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 854 if err != nil { 855 t.Fatalf("failed to create test request: %v", err) 856 } 857 resp, err = client.Do(req) 858 if err != nil { 859 t.Fatalf("Client.Do() error = %v", err) 860 } 861 if resp.StatusCode != http.StatusOK { 862 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 863 } 864 if wantRequestCount += 2; requestCount != wantRequestCount { 865 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 866 } 867 if wantSuccessCount++; successCount != wantSuccessCount { 868 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 869 } 870 if wantAuthCount++; authCount != wantAuthCount { 871 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 872 } 873 } 874 875 func TestClient_Do_Bearer_OAuth2_Password_Cached(t *testing.T) { 876 username := "test_user" 877 password := "test_password" 878 accessToken := "test/access/token" 879 var requestCount, wantRequestCount int64 880 var successCount, wantSuccessCount int64 881 var authCount, wantAuthCount int64 882 var service string 883 scopes := []string{ 884 "repository:dst:pull,push", 885 "repository:src:pull", 886 } 887 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 888 if r.Method != http.MethodPost || r.URL.Path != "/" { 889 t.Error("unexecuted attempt of authorization service") 890 w.WriteHeader(http.StatusUnauthorized) 891 return 892 } 893 if err := r.ParseForm(); err != nil { 894 t.Errorf("failed to parse form: %v", err) 895 w.WriteHeader(http.StatusUnauthorized) 896 return 897 } 898 if got := r.PostForm.Get("grant_type"); got != "password" { 899 t.Errorf("unexpected grant type: %v, want %v", got, "password") 900 w.WriteHeader(http.StatusUnauthorized) 901 return 902 } 903 if got := r.PostForm.Get("service"); got != service { 904 t.Errorf("unexpected service: %v, want %v", got, service) 905 w.WriteHeader(http.StatusUnauthorized) 906 return 907 } 908 if got := r.PostForm.Get("client_id"); got != defaultClientID { 909 t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) 910 w.WriteHeader(http.StatusUnauthorized) 911 return 912 } 913 scope := strings.Join(scopes, " ") 914 if got := r.PostForm.Get("scope"); got != scope { 915 t.Errorf("unexpected scope: %v, want %v", got, scope) 916 w.WriteHeader(http.StatusUnauthorized) 917 return 918 } 919 if got := r.PostForm.Get("username"); got != username { 920 t.Errorf("unexpected username: %v, want %v", got, username) 921 w.WriteHeader(http.StatusUnauthorized) 922 return 923 } 924 if got := r.PostForm.Get("password"); got != password { 925 t.Errorf("unexpected password: %v, want %v", got, password) 926 w.WriteHeader(http.StatusUnauthorized) 927 return 928 } 929 930 atomic.AddInt64(&authCount, 1) 931 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 932 t.Errorf("failed to write %q: %v", r.URL, err) 933 } 934 })) 935 defer as.Close() 936 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 937 atomic.AddInt64(&requestCount, 1) 938 if r.Method != http.MethodGet || r.URL.Path != "/" { 939 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 940 w.WriteHeader(http.StatusNotFound) 941 return 942 } 943 header := "Bearer " + accessToken 944 if auth := r.Header.Get("Authorization"); auth != header { 945 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 946 w.Header().Set("Www-Authenticate", challenge) 947 w.WriteHeader(http.StatusUnauthorized) 948 return 949 } 950 atomic.AddInt64(&successCount, 1) 951 })) 952 defer ts.Close() 953 uri, err := url.Parse(ts.URL) 954 if err != nil { 955 t.Fatalf("invalid test http server: %v", err) 956 } 957 service = uri.Host 958 959 client := &Client{ 960 Credential: func(ctx context.Context, reg string) (Credential, error) { 961 if reg != uri.Host { 962 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 963 t.Error(err) 964 return EmptyCredential, err 965 } 966 return Credential{ 967 Username: username, 968 Password: password, 969 }, nil 970 }, 971 ForceAttemptOAuth2: true, 972 Cache: NewCache(), 973 } 974 975 // first request 976 ctx := WithScopes(context.Background(), scopes...) 977 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 978 if err != nil { 979 t.Fatalf("failed to create test request: %v", err) 980 } 981 resp, err := client.Do(req) 982 if err != nil { 983 t.Fatalf("Client.Do() error = %v", err) 984 } 985 if resp.StatusCode != http.StatusOK { 986 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 987 } 988 if wantRequestCount += 2; requestCount != wantRequestCount { 989 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 990 } 991 if wantSuccessCount++; successCount != wantSuccessCount { 992 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 993 } 994 if wantAuthCount++; authCount != wantAuthCount { 995 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 996 } 997 998 // repeated request 999 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1000 if err != nil { 1001 t.Fatalf("failed to create test request: %v", err) 1002 } 1003 resp, err = client.Do(req) 1004 if err != nil { 1005 t.Fatalf("Client.Do() error = %v", err) 1006 } 1007 if resp.StatusCode != http.StatusOK { 1008 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1009 } 1010 if wantRequestCount++; requestCount != wantRequestCount { 1011 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1012 } 1013 if wantSuccessCount++; successCount != wantSuccessCount { 1014 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1015 } 1016 if authCount != wantAuthCount { 1017 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1018 } 1019 1020 // credential change 1021 username = "test_user2" 1022 password = "test_password2" 1023 accessToken = "test/access/token/2" 1024 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1025 if err != nil { 1026 t.Fatalf("failed to create test request: %v", err) 1027 } 1028 resp, err = client.Do(req) 1029 if err != nil { 1030 t.Fatalf("Client.Do() error = %v", err) 1031 } 1032 if resp.StatusCode != http.StatusOK { 1033 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1034 } 1035 if wantRequestCount += 2; requestCount != wantRequestCount { 1036 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1037 } 1038 if wantSuccessCount++; successCount != wantSuccessCount { 1039 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1040 } 1041 if wantAuthCount++; authCount != wantAuthCount { 1042 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1043 } 1044 } 1045 1046 func TestClient_Do_Bearer_OAuth2_RefreshToken(t *testing.T) { 1047 refreshToken := "test/refresh/token" 1048 accessToken := "test/access/token" 1049 var requestCount, wantRequestCount int64 1050 var successCount, wantSuccessCount int64 1051 var authCount, wantAuthCount int64 1052 var service string 1053 scopes := []string{ 1054 "repository:dst:pull,push", 1055 "repository:src:pull", 1056 } 1057 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1058 if r.Method != http.MethodPost || r.URL.Path != "/" { 1059 t.Error("unexecuted attempt of authorization service") 1060 w.WriteHeader(http.StatusUnauthorized) 1061 return 1062 } 1063 if err := r.ParseForm(); err != nil { 1064 t.Errorf("failed to parse form: %v", err) 1065 w.WriteHeader(http.StatusUnauthorized) 1066 return 1067 } 1068 if got := r.PostForm.Get("grant_type"); got != "refresh_token" { 1069 t.Errorf("unexpected grant type: %v, want %v", got, "refresh_token") 1070 w.WriteHeader(http.StatusUnauthorized) 1071 return 1072 } 1073 if got := r.PostForm.Get("service"); got != service { 1074 t.Errorf("unexpected service: %v, want %v", got, service) 1075 w.WriteHeader(http.StatusUnauthorized) 1076 return 1077 } 1078 if got := r.PostForm.Get("client_id"); got != defaultClientID { 1079 t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) 1080 w.WriteHeader(http.StatusUnauthorized) 1081 return 1082 } 1083 scope := strings.Join(scopes, " ") 1084 if got := r.PostForm.Get("scope"); got != scope { 1085 t.Errorf("unexpected scope: %v, want %v", got, scope) 1086 w.WriteHeader(http.StatusUnauthorized) 1087 return 1088 } 1089 if got := r.PostForm.Get("refresh_token"); got != refreshToken { 1090 t.Errorf("unexpected refresh token: %v, want %v", got, refreshToken) 1091 w.WriteHeader(http.StatusUnauthorized) 1092 return 1093 } 1094 1095 atomic.AddInt64(&authCount, 1) 1096 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 1097 t.Errorf("failed to write %q: %v", r.URL, err) 1098 } 1099 })) 1100 defer as.Close() 1101 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1102 atomic.AddInt64(&requestCount, 1) 1103 if r.Method != http.MethodGet || r.URL.Path != "/" { 1104 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1105 w.WriteHeader(http.StatusNotFound) 1106 return 1107 } 1108 header := "Bearer " + accessToken 1109 if auth := r.Header.Get("Authorization"); auth != header { 1110 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 1111 w.Header().Set("Www-Authenticate", challenge) 1112 w.WriteHeader(http.StatusUnauthorized) 1113 return 1114 } 1115 atomic.AddInt64(&successCount, 1) 1116 })) 1117 defer ts.Close() 1118 uri, err := url.Parse(ts.URL) 1119 if err != nil { 1120 t.Fatalf("invalid test http server: %v", err) 1121 } 1122 service = uri.Host 1123 1124 client := &Client{ 1125 Credential: func(ctx context.Context, reg string) (Credential, error) { 1126 if reg != uri.Host { 1127 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1128 t.Error(err) 1129 return EmptyCredential, err 1130 } 1131 return Credential{ 1132 RefreshToken: refreshToken, 1133 }, nil 1134 }, 1135 } 1136 1137 // first request 1138 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 1139 if err != nil { 1140 t.Fatalf("failed to create test request: %v", err) 1141 } 1142 resp, err := client.Do(req) 1143 if err != nil { 1144 t.Fatalf("Client.Do() error = %v", err) 1145 } 1146 if resp.StatusCode != http.StatusOK { 1147 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1148 } 1149 if wantRequestCount += 2; requestCount != wantRequestCount { 1150 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1151 } 1152 if wantSuccessCount++; successCount != wantSuccessCount { 1153 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1154 } 1155 if wantAuthCount++; authCount != wantAuthCount { 1156 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1157 } 1158 1159 // credential change 1160 refreshToken = "test/refresh/token/2" 1161 accessToken = "test/access/token/2" 1162 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 1163 if err != nil { 1164 t.Fatalf("failed to create test request: %v", err) 1165 } 1166 resp, err = client.Do(req) 1167 if err != nil { 1168 t.Fatalf("Client.Do() error = %v", err) 1169 } 1170 if resp.StatusCode != http.StatusOK { 1171 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1172 } 1173 if wantRequestCount += 2; requestCount != wantRequestCount { 1174 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1175 } 1176 if wantSuccessCount++; successCount != wantSuccessCount { 1177 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1178 } 1179 if wantAuthCount++; authCount != wantAuthCount { 1180 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1181 } 1182 } 1183 1184 func TestClient_Do_Bearer_OAuth2_RefreshToken_Cached(t *testing.T) { 1185 refreshToken := "test/refresh/token" 1186 accessToken := "test/access/token" 1187 var requestCount, wantRequestCount int64 1188 var successCount, wantSuccessCount int64 1189 var authCount, wantAuthCount int64 1190 var service string 1191 scopes := []string{ 1192 "repository:dst:pull,push", 1193 "repository:src:pull", 1194 } 1195 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1196 if r.Method != http.MethodPost || r.URL.Path != "/" { 1197 t.Error("unexecuted attempt of authorization service") 1198 w.WriteHeader(http.StatusUnauthorized) 1199 return 1200 } 1201 if err := r.ParseForm(); err != nil { 1202 t.Errorf("failed to parse form: %v", err) 1203 w.WriteHeader(http.StatusUnauthorized) 1204 return 1205 } 1206 if got := r.PostForm.Get("grant_type"); got != "refresh_token" { 1207 t.Errorf("unexpected grant type: %v, want %v", got, "refresh_token") 1208 w.WriteHeader(http.StatusUnauthorized) 1209 return 1210 } 1211 if got := r.PostForm.Get("service"); got != service { 1212 t.Errorf("unexpected service: %v, want %v", got, service) 1213 w.WriteHeader(http.StatusUnauthorized) 1214 return 1215 } 1216 if got := r.PostForm.Get("client_id"); got != defaultClientID { 1217 t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) 1218 w.WriteHeader(http.StatusUnauthorized) 1219 return 1220 } 1221 scope := strings.Join(scopes, " ") 1222 if got := r.PostForm.Get("scope"); got != scope { 1223 t.Errorf("unexpected scope: %v, want %v", got, scope) 1224 w.WriteHeader(http.StatusUnauthorized) 1225 return 1226 } 1227 if got := r.PostForm.Get("refresh_token"); got != refreshToken { 1228 t.Errorf("unexpected refresh token: %v, want %v", got, refreshToken) 1229 w.WriteHeader(http.StatusUnauthorized) 1230 return 1231 } 1232 1233 atomic.AddInt64(&authCount, 1) 1234 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 1235 t.Errorf("failed to write %q: %v", r.URL, err) 1236 } 1237 })) 1238 defer as.Close() 1239 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1240 atomic.AddInt64(&requestCount, 1) 1241 if r.Method != http.MethodGet || r.URL.Path != "/" { 1242 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1243 w.WriteHeader(http.StatusNotFound) 1244 return 1245 } 1246 header := "Bearer " + accessToken 1247 if auth := r.Header.Get("Authorization"); auth != header { 1248 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 1249 w.Header().Set("Www-Authenticate", challenge) 1250 w.WriteHeader(http.StatusUnauthorized) 1251 return 1252 } 1253 atomic.AddInt64(&successCount, 1) 1254 })) 1255 defer ts.Close() 1256 uri, err := url.Parse(ts.URL) 1257 if err != nil { 1258 t.Fatalf("invalid test http server: %v", err) 1259 } 1260 service = uri.Host 1261 1262 client := &Client{ 1263 Credential: func(ctx context.Context, reg string) (Credential, error) { 1264 if reg != uri.Host { 1265 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1266 t.Error(err) 1267 return EmptyCredential, err 1268 } 1269 return Credential{ 1270 RefreshToken: refreshToken, 1271 }, nil 1272 }, 1273 Cache: NewCache(), 1274 } 1275 1276 // first request 1277 ctx := WithScopes(context.Background(), scopes...) 1278 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1279 if err != nil { 1280 t.Fatalf("failed to create test request: %v", err) 1281 } 1282 resp, err := client.Do(req) 1283 if err != nil { 1284 t.Fatalf("Client.Do() error = %v", err) 1285 } 1286 if resp.StatusCode != http.StatusOK { 1287 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1288 } 1289 if wantRequestCount += 2; requestCount != wantRequestCount { 1290 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1291 } 1292 if wantSuccessCount++; successCount != wantSuccessCount { 1293 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1294 } 1295 if wantAuthCount++; authCount != wantAuthCount { 1296 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1297 } 1298 1299 // repeated request 1300 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1301 if err != nil { 1302 t.Fatalf("failed to create test request: %v", err) 1303 } 1304 resp, err = client.Do(req) 1305 if err != nil { 1306 t.Fatalf("Client.Do() error = %v", err) 1307 } 1308 if resp.StatusCode != http.StatusOK { 1309 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1310 } 1311 if wantRequestCount++; requestCount != wantRequestCount { 1312 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1313 } 1314 if wantSuccessCount++; successCount != wantSuccessCount { 1315 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1316 } 1317 if authCount != wantAuthCount { 1318 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1319 } 1320 1321 // credential change 1322 refreshToken = "test/refresh/token/2" 1323 accessToken = "test/access/token/2" 1324 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1325 if err != nil { 1326 t.Fatalf("failed to create test request: %v", err) 1327 } 1328 resp, err = client.Do(req) 1329 if err != nil { 1330 t.Fatalf("Client.Do() error = %v", err) 1331 } 1332 if resp.StatusCode != http.StatusOK { 1333 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1334 } 1335 if wantRequestCount += 2; requestCount != wantRequestCount { 1336 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1337 } 1338 if wantSuccessCount++; successCount != wantSuccessCount { 1339 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1340 } 1341 if wantAuthCount++; authCount != wantAuthCount { 1342 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1343 } 1344 } 1345 1346 func TestClient_Do_Token_Expire(t *testing.T) { 1347 refreshToken := "test/refresh/token" 1348 accessToken := "test/access/token" 1349 var requestCount, wantRequestCount int64 1350 var successCount, wantSuccessCount int64 1351 var authCount, wantAuthCount int64 1352 var service string 1353 scopes := []string{ 1354 "repository:dst:pull,push", 1355 "repository:src:pull", 1356 } 1357 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1358 if r.Method != http.MethodPost || r.URL.Path != "/" { 1359 t.Error("unexecuted attempt of authorization service") 1360 w.WriteHeader(http.StatusUnauthorized) 1361 return 1362 } 1363 if err := r.ParseForm(); err != nil { 1364 t.Errorf("failed to parse form: %v", err) 1365 w.WriteHeader(http.StatusUnauthorized) 1366 return 1367 } 1368 if got := r.PostForm.Get("grant_type"); got != "refresh_token" { 1369 t.Errorf("unexpected grant type: %v, want %v", got, "refresh_token") 1370 w.WriteHeader(http.StatusUnauthorized) 1371 return 1372 } 1373 if got := r.PostForm.Get("service"); got != service { 1374 t.Errorf("unexpected service: %v, want %v", got, service) 1375 w.WriteHeader(http.StatusUnauthorized) 1376 return 1377 } 1378 if got := r.PostForm.Get("client_id"); got != defaultClientID { 1379 t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) 1380 w.WriteHeader(http.StatusUnauthorized) 1381 return 1382 } 1383 scope := strings.Join(scopes, " ") 1384 if got := r.PostForm.Get("scope"); got != scope { 1385 t.Errorf("unexpected scope: %v, want %v", got, scope) 1386 w.WriteHeader(http.StatusUnauthorized) 1387 return 1388 } 1389 if got := r.PostForm.Get("refresh_token"); got != refreshToken { 1390 t.Errorf("unexpected refresh token: %v, want %v", got, refreshToken) 1391 w.WriteHeader(http.StatusUnauthorized) 1392 return 1393 } 1394 1395 atomic.AddInt64(&authCount, 1) 1396 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 1397 t.Errorf("failed to write %q: %v", r.URL, err) 1398 } 1399 })) 1400 defer as.Close() 1401 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1402 atomic.AddInt64(&requestCount, 1) 1403 if r.Method != http.MethodGet || r.URL.Path != "/" { 1404 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1405 w.WriteHeader(http.StatusNotFound) 1406 return 1407 } 1408 header := "Bearer " + accessToken 1409 if auth := r.Header.Get("Authorization"); auth != header { 1410 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 1411 w.Header().Set("Www-Authenticate", challenge) 1412 w.WriteHeader(http.StatusUnauthorized) 1413 return 1414 } 1415 atomic.AddInt64(&successCount, 1) 1416 })) 1417 defer ts.Close() 1418 uri, err := url.Parse(ts.URL) 1419 if err != nil { 1420 t.Fatalf("invalid test http server: %v", err) 1421 } 1422 service = uri.Host 1423 1424 client := &Client{ 1425 Credential: func(ctx context.Context, reg string) (Credential, error) { 1426 if reg != uri.Host { 1427 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1428 t.Error(err) 1429 return EmptyCredential, err 1430 } 1431 return Credential{ 1432 RefreshToken: refreshToken, 1433 }, nil 1434 }, 1435 Cache: NewCache(), 1436 } 1437 1438 // first request 1439 ctx := WithScopes(context.Background(), scopes...) 1440 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1441 if err != nil { 1442 t.Fatalf("failed to create test request: %v", err) 1443 } 1444 resp, err := client.Do(req) 1445 if err != nil { 1446 t.Fatalf("Client.Do() error = %v", err) 1447 } 1448 if resp.StatusCode != http.StatusOK { 1449 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1450 } 1451 if wantRequestCount += 2; requestCount != wantRequestCount { 1452 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1453 } 1454 if wantSuccessCount++; successCount != wantSuccessCount { 1455 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1456 } 1457 if wantAuthCount++; authCount != wantAuthCount { 1458 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1459 } 1460 1461 // invalidate the access token and request again 1462 accessToken = "test/access/token/2" 1463 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1464 if err != nil { 1465 t.Fatalf("failed to create test request: %v", err) 1466 } 1467 resp, err = client.Do(req) 1468 if err != nil { 1469 t.Fatalf("Client.Do() error = %v", err) 1470 } 1471 if resp.StatusCode != http.StatusOK { 1472 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1473 } 1474 if wantRequestCount += 2; requestCount != wantRequestCount { 1475 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1476 } 1477 if wantSuccessCount++; successCount != wantSuccessCount { 1478 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1479 } 1480 if wantAuthCount++; authCount != wantAuthCount { 1481 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1482 } 1483 } 1484 1485 func TestClient_Do_Scope_Hint_Mismatch(t *testing.T) { 1486 username := "test_user" 1487 password := "test_password" 1488 accessToken := "test/access/token" 1489 var requestCount, wantRequestCount int64 1490 var successCount, wantSuccessCount int64 1491 var authCount, wantAuthCount int64 1492 var service string 1493 scopes := []string{ 1494 "repository:dst:pull,push", 1495 "repository:src:pull", 1496 } 1497 scope := "repository:test:delete" 1498 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1499 if r.Method != http.MethodPost || r.URL.Path != "/" { 1500 t.Error("unexecuted attempt of authorization service") 1501 w.WriteHeader(http.StatusUnauthorized) 1502 return 1503 } 1504 if err := r.ParseForm(); err != nil { 1505 t.Errorf("failed to parse form: %v", err) 1506 w.WriteHeader(http.StatusUnauthorized) 1507 return 1508 } 1509 if got := r.PostForm.Get("grant_type"); got != "password" { 1510 t.Errorf("unexpected grant type: %v, want %v", got, "password") 1511 w.WriteHeader(http.StatusUnauthorized) 1512 return 1513 } 1514 if got := r.PostForm.Get("service"); got != service { 1515 t.Errorf("unexpected service: %v, want %v", got, service) 1516 w.WriteHeader(http.StatusUnauthorized) 1517 return 1518 } 1519 if got := r.PostForm.Get("client_id"); got != defaultClientID { 1520 t.Errorf("unexpected client id: %v, want %v", got, defaultClientID) 1521 w.WriteHeader(http.StatusUnauthorized) 1522 return 1523 } 1524 scopes := CleanScopes(append([]string{scope}, scopes...)) 1525 scope := strings.Join(scopes, " ") 1526 if got := r.PostForm.Get("scope"); got != scope { 1527 t.Errorf("unexpected scope: %v, want %v", got, scope) 1528 w.WriteHeader(http.StatusUnauthorized) 1529 return 1530 } 1531 if got := r.PostForm.Get("username"); got != username { 1532 t.Errorf("unexpected username: %v, want %v", got, username) 1533 w.WriteHeader(http.StatusUnauthorized) 1534 return 1535 } 1536 if got := r.PostForm.Get("password"); got != password { 1537 t.Errorf("unexpected password: %v, want %v", got, password) 1538 w.WriteHeader(http.StatusUnauthorized) 1539 return 1540 } 1541 1542 atomic.AddInt64(&authCount, 1) 1543 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 1544 t.Errorf("failed to write %q: %v", r.URL, err) 1545 } 1546 })) 1547 defer as.Close() 1548 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1549 atomic.AddInt64(&requestCount, 1) 1550 if r.Method != http.MethodGet || r.URL.Path != "/" { 1551 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1552 w.WriteHeader(http.StatusNotFound) 1553 return 1554 } 1555 header := "Bearer " + accessToken 1556 if auth := r.Header.Get("Authorization"); auth != header { 1557 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) 1558 w.Header().Set("Www-Authenticate", challenge) 1559 w.WriteHeader(http.StatusUnauthorized) 1560 return 1561 } 1562 atomic.AddInt64(&successCount, 1) 1563 })) 1564 defer ts.Close() 1565 uri, err := url.Parse(ts.URL) 1566 if err != nil { 1567 t.Fatalf("invalid test http server: %v", err) 1568 } 1569 service = uri.Host 1570 1571 client := &Client{ 1572 Credential: func(ctx context.Context, reg string) (Credential, error) { 1573 if reg != uri.Host { 1574 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1575 t.Error(err) 1576 return EmptyCredential, err 1577 } 1578 return Credential{ 1579 Username: username, 1580 Password: password, 1581 }, nil 1582 }, 1583 ForceAttemptOAuth2: true, 1584 Cache: NewCache(), 1585 } 1586 1587 // first request 1588 ctx := WithScopes(context.Background(), scopes...) 1589 req, err := http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1590 if err != nil { 1591 t.Fatalf("failed to create test request: %v", err) 1592 } 1593 resp, err := client.Do(req) 1594 if err != nil { 1595 t.Fatalf("Client.Do() error = %v", err) 1596 } 1597 if resp.StatusCode != http.StatusOK { 1598 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1599 } 1600 if wantRequestCount += 2; requestCount != wantRequestCount { 1601 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1602 } 1603 if wantSuccessCount++; successCount != wantSuccessCount { 1604 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1605 } 1606 if wantAuthCount++; authCount != wantAuthCount { 1607 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1608 } 1609 1610 // repeated request 1611 // although the actual scope does not match the hinted scopes, the client 1612 // with cache cannot avoid a request to obtain a challenge but can prevent 1613 // a repeated call to the authorization server. 1614 req, err = http.NewRequestWithContext(ctx, http.MethodGet, ts.URL, nil) 1615 if err != nil { 1616 t.Fatalf("failed to create test request: %v", err) 1617 } 1618 resp, err = client.Do(req) 1619 if err != nil { 1620 t.Fatalf("Client.Do() error = %v", err) 1621 } 1622 if resp.StatusCode != http.StatusOK { 1623 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1624 } 1625 if wantRequestCount += 2; requestCount != wantRequestCount { 1626 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1627 } 1628 if wantSuccessCount++; successCount != wantSuccessCount { 1629 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1630 } 1631 if authCount != wantAuthCount { 1632 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1633 } 1634 } 1635 1636 func TestClient_Do_Invalid_Credential_Basic(t *testing.T) { 1637 username := "test_user" 1638 password := "test_password" 1639 var requestCount, wantRequestCount int64 1640 var successCount, wantSuccessCount int64 1641 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1642 atomic.AddInt64(&requestCount, 1) 1643 if r.Method != http.MethodGet || r.URL.Path != "/" { 1644 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1645 w.WriteHeader(http.StatusNotFound) 1646 return 1647 } 1648 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 1649 if auth := r.Header.Get("Authorization"); auth != header { 1650 w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) 1651 w.WriteHeader(http.StatusUnauthorized) 1652 return 1653 } 1654 atomic.AddInt64(&successCount, 1) 1655 t.Error("authentication should fail but succeeded") 1656 })) 1657 defer ts.Close() 1658 uri, err := url.Parse(ts.URL) 1659 if err != nil { 1660 t.Fatalf("invalid test http server: %v", err) 1661 } 1662 1663 client := &Client{ 1664 Credential: func(ctx context.Context, reg string) (Credential, error) { 1665 if reg != uri.Host { 1666 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1667 t.Error(err) 1668 return EmptyCredential, err 1669 } 1670 return Credential{ 1671 Username: username, 1672 Password: "bad credential", 1673 }, nil 1674 }, 1675 } 1676 1677 // request should fail 1678 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 1679 if err != nil { 1680 t.Fatalf("failed to create test request: %v", err) 1681 } 1682 resp, err := client.Do(req) 1683 if err != nil { 1684 t.Fatalf("Client.Do() error = %v", err) 1685 } 1686 if resp.StatusCode != http.StatusUnauthorized { 1687 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusUnauthorized) 1688 } 1689 if wantRequestCount += 2; requestCount != wantRequestCount { 1690 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1691 } 1692 if successCount != wantSuccessCount { 1693 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1694 } 1695 } 1696 1697 func TestClient_Do_Invalid_Credential_Bearer(t *testing.T) { 1698 username := "test_user" 1699 password := "test_password" 1700 accessToken := "test/access/token" 1701 var requestCount, wantRequestCount int64 1702 var successCount, wantSuccessCount int64 1703 var authCount, wantAuthCount int64 1704 var service string 1705 scopes := []string{ 1706 "repository:dst:pull,push", 1707 "repository:src:pull", 1708 } 1709 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1710 if r.Method != http.MethodGet || r.URL.Path != "/" { 1711 t.Error("unexecuted attempt of authorization service") 1712 w.WriteHeader(http.StatusUnauthorized) 1713 return 1714 } 1715 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 1716 if auth := r.Header.Get("Authorization"); auth != header { 1717 atomic.AddInt64(&authCount, 1) 1718 w.WriteHeader(http.StatusUnauthorized) 1719 return 1720 } 1721 t.Error("authentication should fail but succeeded") 1722 })) 1723 defer as.Close() 1724 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1725 atomic.AddInt64(&requestCount, 1) 1726 if r.Method != http.MethodGet || r.URL.Path != "/" { 1727 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1728 w.WriteHeader(http.StatusNotFound) 1729 return 1730 } 1731 header := "Bearer " + accessToken 1732 if auth := r.Header.Get("Authorization"); auth != header { 1733 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, strings.Join(scopes, " ")) 1734 w.Header().Set("Www-Authenticate", challenge) 1735 w.WriteHeader(http.StatusUnauthorized) 1736 return 1737 } 1738 atomic.AddInt64(&successCount, 1) 1739 t.Error("authentication should fail but succeeded") 1740 })) 1741 defer ts.Close() 1742 uri, err := url.Parse(ts.URL) 1743 if err != nil { 1744 t.Fatalf("invalid test http server: %v", err) 1745 } 1746 service = uri.Host 1747 1748 client := &Client{ 1749 Credential: func(ctx context.Context, reg string) (Credential, error) { 1750 if reg != uri.Host { 1751 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1752 t.Error(err) 1753 return EmptyCredential, err 1754 } 1755 return Credential{ 1756 Username: username, 1757 Password: "bad credential", 1758 }, nil 1759 }, 1760 } 1761 1762 // request should fail 1763 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 1764 if err != nil { 1765 t.Fatalf("failed to create test request: %v", err) 1766 } 1767 _, err = client.Do(req) 1768 if err == nil { 1769 t.Fatalf("Client.Do() error = %v, wantErr %v", err, true) 1770 } 1771 if wantRequestCount++; requestCount != wantRequestCount { 1772 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1773 } 1774 if successCount != wantSuccessCount { 1775 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1776 } 1777 if wantAuthCount++; authCount != wantAuthCount { 1778 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1779 } 1780 } 1781 1782 func TestClient_Do_Anonymous_Pull(t *testing.T) { 1783 accessToken := "test/access/token" 1784 var requestCount, wantRequestCount int64 1785 var successCount, wantSuccessCount int64 1786 var authCount, wantAuthCount int64 1787 var service string 1788 scope := "repository:test:pull" 1789 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1790 if r.Method != http.MethodGet || r.URL.Path != "/" { 1791 t.Error("unexecuted attempt of authorization service") 1792 w.WriteHeader(http.StatusUnauthorized) 1793 return 1794 } 1795 if auth := r.Header.Get("Authorization"); auth != "" { 1796 t.Errorf("unexpected auth: got %s, want %s", auth, "") 1797 w.WriteHeader(http.StatusUnauthorized) 1798 return 1799 } 1800 if got := r.URL.Query().Get("service"); got != service { 1801 t.Errorf("unexpected service: got %s, want %s", got, service) 1802 w.WriteHeader(http.StatusUnauthorized) 1803 return 1804 } 1805 if got := r.URL.Query().Get("scope"); got != scope { 1806 t.Errorf("unexpected scope: got %s, want %s", got, scope) 1807 w.WriteHeader(http.StatusUnauthorized) 1808 return 1809 } 1810 1811 atomic.AddInt64(&authCount, 1) 1812 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 1813 t.Errorf("failed to write %q: %v", r.URL, err) 1814 } 1815 })) 1816 defer as.Close() 1817 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1818 atomic.AddInt64(&requestCount, 1) 1819 if r.Method != http.MethodGet || r.URL.Path != "/" { 1820 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1821 w.WriteHeader(http.StatusNotFound) 1822 return 1823 } 1824 header := "Bearer " + accessToken 1825 if auth := r.Header.Get("Authorization"); auth != header { 1826 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) 1827 w.Header().Set("Www-Authenticate", challenge) 1828 w.WriteHeader(http.StatusUnauthorized) 1829 return 1830 } 1831 atomic.AddInt64(&successCount, 1) 1832 })) 1833 defer ts.Close() 1834 uri, err := url.Parse(ts.URL) 1835 if err != nil { 1836 t.Fatalf("invalid test http server: %v", err) 1837 } 1838 service = uri.Host 1839 1840 // request with the default client 1841 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 1842 if err != nil { 1843 t.Fatalf("failed to create test request: %v", err) 1844 } 1845 resp, err := DefaultClient.Do(req) 1846 if err != nil { 1847 t.Fatalf("Client.Do() error = %v", err) 1848 } 1849 if resp.StatusCode != http.StatusOK { 1850 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1851 } 1852 if wantRequestCount += 2; requestCount != wantRequestCount { 1853 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1854 } 1855 if wantSuccessCount++; successCount != wantSuccessCount { 1856 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1857 } 1858 if wantAuthCount++; authCount != wantAuthCount { 1859 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1860 } 1861 } 1862 1863 func TestClient_Do_Scheme_Change(t *testing.T) { 1864 username := "test_user" 1865 password := "test_password" 1866 accessToken := "test/access/token" 1867 var requestCount, wantRequestCount int64 1868 var successCount, wantSuccessCount int64 1869 var authCount, wantAuthCount int64 1870 var service string 1871 scope := "repository:test:pull" 1872 challengeBearerAuth := true 1873 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1874 if r.Method != http.MethodGet || r.URL.Path != "/" { 1875 t.Error("unexecuted attempt of authorization service") 1876 w.WriteHeader(http.StatusUnauthorized) 1877 return 1878 } 1879 header := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 1880 if auth := r.Header.Get("Authorization"); auth != header { 1881 t.Errorf("unexpected auth: got %s, want %s", auth, header) 1882 w.WriteHeader(http.StatusUnauthorized) 1883 return 1884 } 1885 if got := r.URL.Query().Get("service"); got != service { 1886 t.Errorf("unexpected service: got %s, want %s", got, service) 1887 w.WriteHeader(http.StatusUnauthorized) 1888 return 1889 } 1890 if got := r.URL.Query().Get("scope"); got != scope { 1891 t.Errorf("unexpected scope: got %s, want %s", got, scope) 1892 w.WriteHeader(http.StatusUnauthorized) 1893 return 1894 } 1895 1896 atomic.AddInt64(&authCount, 1) 1897 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, accessToken); err != nil { 1898 t.Errorf("failed to write %q: %v", r.URL, err) 1899 } 1900 })) 1901 defer as.Close() 1902 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1903 atomic.AddInt64(&requestCount, 1) 1904 if r.Method != http.MethodGet || r.URL.Path != "/" { 1905 t.Errorf("unexpected access: %s %s", r.Method, r.URL) 1906 w.WriteHeader(http.StatusNotFound) 1907 return 1908 } 1909 bearerHeader := "Bearer " + accessToken 1910 basicHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password)) 1911 header := r.Header.Get("Authorization") 1912 if (challengeBearerAuth && header != bearerHeader) || (!challengeBearerAuth && header != basicHeader) { 1913 var challenge string 1914 if challengeBearerAuth { 1915 challenge = fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, service, scope) 1916 } else { 1917 challenge = `Basic realm="Test Server"` 1918 } 1919 w.Header().Set("Www-Authenticate", challenge) 1920 w.WriteHeader(http.StatusUnauthorized) 1921 return 1922 } 1923 atomic.AddInt64(&successCount, 1) 1924 })) 1925 defer ts.Close() 1926 uri, err := url.Parse(ts.URL) 1927 if err != nil { 1928 t.Fatalf("invalid test http server: %v", err) 1929 } 1930 service = uri.Host 1931 1932 client := &Client{ 1933 Credential: func(ctx context.Context, reg string) (Credential, error) { 1934 if reg != uri.Host { 1935 err := fmt.Errorf("registry mismatch: got %v, want %v", reg, uri.Host) 1936 t.Error(err) 1937 return EmptyCredential, err 1938 } 1939 return Credential{ 1940 Username: username, 1941 Password: password, 1942 }, nil 1943 }, 1944 Cache: NewCache(), 1945 } 1946 1947 // request with bearer auth 1948 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 1949 if err != nil { 1950 t.Fatalf("failed to create test request: %v", err) 1951 } 1952 resp, err := client.Do(req) 1953 if err != nil { 1954 t.Fatalf("Client.Do() error = %v", err) 1955 } 1956 if resp.StatusCode != http.StatusOK { 1957 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1958 } 1959 if wantRequestCount += 2; requestCount != wantRequestCount { 1960 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1961 } 1962 if wantSuccessCount++; successCount != wantSuccessCount { 1963 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1964 } 1965 if wantAuthCount++; authCount != wantAuthCount { 1966 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1967 } 1968 1969 // change to basic auth 1970 challengeBearerAuth = false 1971 req, err = http.NewRequest(http.MethodGet, ts.URL, nil) 1972 if err != nil { 1973 t.Fatalf("failed to create test request: %v", err) 1974 } 1975 resp, err = client.Do(req) 1976 if err != nil { 1977 t.Fatalf("Client.Do() error = %v", err) 1978 } 1979 if resp.StatusCode != http.StatusOK { 1980 t.Errorf("Client.Do() = %v, want %v", resp.StatusCode, http.StatusOK) 1981 } 1982 if wantRequestCount += 2; requestCount != wantRequestCount { 1983 t.Errorf("unexpected number of requests: %d, want %d", requestCount, wantRequestCount) 1984 } 1985 if wantSuccessCount++; successCount != wantSuccessCount { 1986 t.Errorf("unexpected number of successful requests: %d, want %d", successCount, wantSuccessCount) 1987 } 1988 if authCount != wantAuthCount { 1989 t.Errorf("unexpected number of auth requests: %d, want %d", authCount, wantAuthCount) 1990 } 1991 } 1992 1993 func TestClient_StaticCredential_basicAuth(t *testing.T) { 1994 testUsername := "username" 1995 testPassword := "password" 1996 1997 // create a test server 1998 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 1999 path := r.URL.Path 2000 if r.Method != http.MethodGet { 2001 w.WriteHeader(http.StatusNotFound) 2002 t.Fatal("unexpected access") 2003 } 2004 switch path { 2005 case "/basicAuth": 2006 wantedAuthHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(testUsername+":"+testPassword)) 2007 authHeader := r.Header.Get("Authorization") 2008 if authHeader != wantedAuthHeader { 2009 w.Header().Set("Www-Authenticate", `Basic realm="Test Server"`) 2010 w.WriteHeader(http.StatusUnauthorized) 2011 } 2012 default: 2013 w.WriteHeader(http.StatusNotAcceptable) 2014 } 2015 })) 2016 defer ts.Close() 2017 host := ts.URL 2018 uri, _ := url.Parse(host) 2019 hostAddress := uri.Host 2020 basicAuthURL := fmt.Sprintf("%s/basicAuth", host) 2021 2022 // create a test client with the correct credentials 2023 clientValid := &Client{ 2024 Credential: StaticCredential(hostAddress, Credential{ 2025 Username: testUsername, 2026 Password: testPassword, 2027 }), 2028 } 2029 req, err := http.NewRequest(http.MethodGet, basicAuthURL, nil) 2030 if err != nil { 2031 t.Fatalf("could not create request, err = %v", err) 2032 } 2033 respValid, err := clientValid.Do(req) 2034 if err != nil { 2035 t.Fatalf("could not send request, err = %v", err) 2036 } 2037 if respValid.StatusCode != 200 { 2038 t.Errorf("incorrect status code: %d, expected 200", respValid.StatusCode) 2039 } 2040 2041 // create a test client with incorrect credentials 2042 clientInvalid := &Client{ 2043 Credential: StaticCredential(hostAddress, Credential{ 2044 Username: "foo", 2045 Password: "bar", 2046 }), 2047 } 2048 respInvalid, err := clientInvalid.Do(req) 2049 if err != nil { 2050 t.Fatalf("could not send request, err = %v", err) 2051 } 2052 if respInvalid.StatusCode != 401 { 2053 t.Errorf("incorrect status code: %d, expected 401", respInvalid.StatusCode) 2054 } 2055 } 2056 2057 func TestClient_StaticCredential_withAccessToken(t *testing.T) { 2058 var host string 2059 testAccessToken := "test/access/token" 2060 scope := "repository:test:pull,push" 2061 2062 // create an authorization server 2063 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2064 w.WriteHeader(http.StatusUnauthorized) 2065 t.Error("unexecuted attempt of authorization service") 2066 })) 2067 defer as.Close() 2068 2069 // create a test server 2070 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2071 path := r.URL.Path 2072 if r.Method != http.MethodGet { 2073 w.WriteHeader(http.StatusNotFound) 2074 t.Fatal("unexpected access") 2075 } 2076 switch path { 2077 case "/accessToken": 2078 wantedAuthHeader := "Bearer " + testAccessToken 2079 if auth := r.Header.Get("Authorization"); auth != wantedAuthHeader { 2080 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, host, scope) 2081 w.Header().Set("Www-Authenticate", challenge) 2082 w.WriteHeader(http.StatusUnauthorized) 2083 } 2084 default: 2085 w.WriteHeader(http.StatusNotAcceptable) 2086 } 2087 })) 2088 defer ts.Close() 2089 host = ts.URL 2090 uri, _ := url.Parse(host) 2091 hostAddress := uri.Host 2092 accessTokenURL := fmt.Sprintf("%s/accessToken", host) 2093 2094 // create a test client with the correct credentials 2095 clientValid := &Client{ 2096 Credential: StaticCredential(hostAddress, Credential{ 2097 AccessToken: testAccessToken, 2098 }), 2099 } 2100 req, err := http.NewRequest(http.MethodGet, accessTokenURL, nil) 2101 if err != nil { 2102 t.Fatalf("could not create request, err = %v", err) 2103 } 2104 respValid, err := clientValid.Do(req) 2105 if err != nil { 2106 t.Fatalf("could not send request, err = %v", err) 2107 } 2108 if respValid.StatusCode != 200 { 2109 t.Errorf("incorrect status code: %d, expected 200", respValid.StatusCode) 2110 } 2111 2112 // create a test client with incorrect credentials 2113 clientInvalid := &Client{ 2114 Credential: StaticCredential(hostAddress, Credential{ 2115 AccessToken: "foo", 2116 }), 2117 } 2118 respInvalid, err := clientInvalid.Do(req) 2119 if err != nil { 2120 t.Fatalf("could not send request, err = %v", err) 2121 } 2122 if respInvalid.StatusCode != 401 { 2123 t.Errorf("incorrect status code: %d, expected 401", respInvalid.StatusCode) 2124 } 2125 } 2126 2127 func TestClient_StaticCredential_withRefreshToken(t *testing.T) { 2128 var host string 2129 testAccessToken := "test/access/token" 2130 testRefreshToken := "test/refresh/token" 2131 scope := "repository:test:pull,push" 2132 2133 // create an authorization server 2134 as := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2135 if r.Method != http.MethodGet && r.Method != http.MethodPost { 2136 w.WriteHeader(http.StatusUnauthorized) 2137 t.Error("unexecuted attempt of authorization service") 2138 } 2139 if err := r.ParseForm(); err != nil { 2140 w.WriteHeader(http.StatusUnauthorized) 2141 t.Error("failed to parse form") 2142 } 2143 if got := r.PostForm.Get("service"); got != host { 2144 w.WriteHeader(http.StatusUnauthorized) 2145 } 2146 // handles refresh token requests 2147 if got := r.PostForm.Get("grant_type"); got != "refresh_token" { 2148 w.WriteHeader(http.StatusUnauthorized) 2149 } 2150 if got := r.PostForm.Get("scope"); got != scope { 2151 w.WriteHeader(http.StatusUnauthorized) 2152 } 2153 if got := r.PostForm.Get("refresh_token"); got != testRefreshToken { 2154 w.WriteHeader(http.StatusUnauthorized) 2155 } 2156 // writes back access token 2157 if _, err := fmt.Fprintf(w, `{"access_token":%q}`, testAccessToken); err != nil { 2158 t.Fatalf("could not write back access token, error = %v", err) 2159 } 2160 })) 2161 defer as.Close() 2162 2163 // create a test server 2164 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 2165 path := r.URL.Path 2166 if r.Method != http.MethodGet { 2167 w.WriteHeader(http.StatusNotFound) 2168 panic("unexpected access") 2169 } 2170 switch path { 2171 case "/refreshToken": 2172 wantedAuthHeader := "Bearer " + testAccessToken 2173 if auth := r.Header.Get("Authorization"); auth != wantedAuthHeader { 2174 challenge := fmt.Sprintf("Bearer realm=%q,service=%q,scope=%q", as.URL, host, scope) 2175 w.Header().Set("Www-Authenticate", challenge) 2176 w.WriteHeader(http.StatusUnauthorized) 2177 } 2178 default: 2179 w.WriteHeader(http.StatusNotAcceptable) 2180 } 2181 })) 2182 defer ts.Close() 2183 host = ts.URL 2184 uri, _ := url.Parse(host) 2185 hostAddress := uri.Host 2186 refreshTokenURL := fmt.Sprintf("%s/refreshToken", host) 2187 2188 // create a test client with the correct credentials 2189 clientValid := &Client{ 2190 Credential: StaticCredential(hostAddress, Credential{ 2191 RefreshToken: testRefreshToken, 2192 }), 2193 } 2194 req, err := http.NewRequest(http.MethodGet, refreshTokenURL, nil) 2195 if err != nil { 2196 t.Fatalf("could not create request, err = %v", err) 2197 } 2198 respValid, err := clientValid.Do(req) 2199 if err != nil { 2200 t.Fatalf("could not send request, err = %v", err) 2201 } 2202 if respValid.StatusCode != 200 { 2203 t.Errorf("incorrect status code: %d, expected 200", respValid.StatusCode) 2204 } 2205 2206 // create a test client with incorrect credentials 2207 clientInvalid := &Client{ 2208 Credential: StaticCredential(hostAddress, Credential{ 2209 RefreshToken: "bar", 2210 }), 2211 } 2212 _, err = clientInvalid.Do(req) 2213 2214 var expectedError *errcode.ErrorResponse 2215 if !errors.As(err, &expectedError) || expectedError.StatusCode != http.StatusUnauthorized { 2216 t.Errorf("incorrect error: %v, expected %v", err, expectedError) 2217 } 2218 } 2219 2220 func TestClient_StaticCredential_registryMismatch(t *testing.T) { 2221 testUsername := "username" 2222 testPassword := "password" 2223 targetAddress := "target/address" 2224 2225 client := &Client{ 2226 Credential: StaticCredential(targetAddress, Credential{ 2227 Username: testUsername, 2228 Password: testPassword, 2229 }), 2230 } 2231 2232 cred, err := client.Credential(context.Background(), "registry/mismatched") 2233 if cred != EmptyCredential { 2234 t.Errorf("Credential() = %v, want = %v", cred, EmptyCredential) 2235 } 2236 if err != nil { 2237 t.Errorf("got error = %v, expected error = %v", err, nil) 2238 } 2239 }