...

Source file src/oras.land/oras-go/pkg/registry/remote/auth/client_test.go

Documentation: oras.land/oras-go/pkg/registry/remote/auth

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

View as plain text