...

Source file src/github.com/docker/distribution/registry/client/auth/session_test.go

Documentation: github.com/docker/distribution/registry/client/auth

     1  package auth
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/docker/distribution/registry/client/auth/challenge"
    13  	"github.com/docker/distribution/registry/client/transport"
    14  	"github.com/docker/distribution/testutil"
    15  )
    16  
    17  // An implementation of clock for providing fake time data.
    18  type fakeClock struct {
    19  	current time.Time
    20  }
    21  
    22  // Now implements clock
    23  func (fc *fakeClock) Now() time.Time { return fc.current }
    24  
    25  func testServer(rrm testutil.RequestResponseMap) (string, func()) {
    26  	h := testutil.NewHandler(rrm)
    27  	s := httptest.NewServer(h)
    28  	return s.URL, s.Close
    29  }
    30  
    31  type testAuthenticationWrapper struct {
    32  	headers   http.Header
    33  	authCheck func(string) bool
    34  	next      http.Handler
    35  }
    36  
    37  func (w *testAuthenticationWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    38  	auth := r.Header.Get("Authorization")
    39  	if auth == "" || !w.authCheck(auth) {
    40  		h := rw.Header()
    41  		for k, values := range w.headers {
    42  			h[k] = values
    43  		}
    44  		rw.WriteHeader(http.StatusUnauthorized)
    45  		return
    46  	}
    47  	w.next.ServeHTTP(rw, r)
    48  }
    49  
    50  func testServerWithAuth(rrm testutil.RequestResponseMap, authenticate string, authCheck func(string) bool) (string, func()) {
    51  	h := testutil.NewHandler(rrm)
    52  	wrapper := &testAuthenticationWrapper{
    53  
    54  		headers: http.Header(map[string][]string{
    55  			"X-API-Version":       {"registry/2.0"},
    56  			"X-Multi-API-Version": {"registry/2.0", "registry/2.1", "trust/1.0"},
    57  			"WWW-Authenticate":    {authenticate},
    58  		}),
    59  		authCheck: authCheck,
    60  		next:      h,
    61  	}
    62  
    63  	s := httptest.NewServer(wrapper)
    64  	return s.URL, s.Close
    65  }
    66  
    67  // ping pings the provided endpoint to determine its required authorization challenges.
    68  // If a version header is provided, the versions will be returned.
    69  func ping(manager challenge.Manager, endpoint, versionHeader string) ([]APIVersion, error) {
    70  	resp, err := http.Get(endpoint)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	defer resp.Body.Close()
    75  
    76  	if err := manager.AddResponse(resp); err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	return APIVersions(resp, versionHeader), err
    81  }
    82  
    83  type testCredentialStore struct {
    84  	username      string
    85  	password      string
    86  	refreshTokens map[string]string
    87  }
    88  
    89  func (tcs *testCredentialStore) Basic(*url.URL) (string, string) {
    90  	return tcs.username, tcs.password
    91  }
    92  
    93  func (tcs *testCredentialStore) RefreshToken(u *url.URL, service string) string {
    94  	return tcs.refreshTokens[service]
    95  }
    96  
    97  func (tcs *testCredentialStore) SetRefreshToken(u *url.URL, service string, token string) {
    98  	if tcs.refreshTokens != nil {
    99  		tcs.refreshTokens[service] = token
   100  	}
   101  }
   102  
   103  func TestEndpointAuthorizeToken(t *testing.T) {
   104  	service := "localhost.localdomain"
   105  	repo1 := "some/registry"
   106  	repo2 := "other/registry"
   107  	scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
   108  	scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
   109  	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   110  		{
   111  			Request: testutil.Request{
   112  				Method: "GET",
   113  				Route:  fmt.Sprintf("/token?scope=%s&service=%s", url.QueryEscape(scope1), service),
   114  			},
   115  			Response: testutil.Response{
   116  				StatusCode: http.StatusOK,
   117  				Body:       []byte(`{"token":"statictoken"}`),
   118  			},
   119  		},
   120  		{
   121  			Request: testutil.Request{
   122  				Method: "GET",
   123  				Route:  fmt.Sprintf("/token?scope=%s&service=%s", url.QueryEscape(scope2), service),
   124  			},
   125  			Response: testutil.Response{
   126  				StatusCode: http.StatusOK,
   127  				Body:       []byte(`{"token":"badtoken"}`),
   128  			},
   129  		},
   130  	})
   131  	te, tc := testServer(tokenMap)
   132  	defer tc()
   133  
   134  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   135  		{
   136  			Request: testutil.Request{
   137  				Method: "GET",
   138  				Route:  "/v2/hello",
   139  			},
   140  			Response: testutil.Response{
   141  				StatusCode: http.StatusAccepted,
   142  			},
   143  		},
   144  	})
   145  
   146  	authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
   147  	validCheck := func(a string) bool {
   148  		return a == "Bearer statictoken"
   149  	}
   150  	e, c := testServerWithAuth(m, authenicate, validCheck)
   151  	defer c()
   152  
   153  	challengeManager1 := challenge.NewSimpleManager()
   154  	versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
   155  	if err != nil {
   156  		t.Fatal(err)
   157  	}
   158  	if len(versions) != 1 {
   159  		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
   160  	}
   161  	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
   162  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
   163  	}
   164  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, nil, repo1, "pull", "push")))
   165  	client := &http.Client{Transport: transport1}
   166  
   167  	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   168  	resp, err := client.Do(req)
   169  	if err != nil {
   170  		t.Fatalf("Error sending get request: %s", err)
   171  	}
   172  
   173  	if resp.StatusCode != http.StatusAccepted {
   174  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   175  	}
   176  
   177  	e2, c2 := testServerWithAuth(m, authenicate, validCheck)
   178  	defer c2()
   179  
   180  	challengeManager2 := challenge.NewSimpleManager()
   181  	versions, err = ping(challengeManager2, e2+"/v2/", "x-multi-api-version")
   182  	if err != nil {
   183  		t.Fatal(err)
   184  	}
   185  	if len(versions) != 3 {
   186  		t.Fatalf("Unexpected version count: %d, expected 3", len(versions))
   187  	}
   188  	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
   189  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
   190  	}
   191  	if check := (APIVersion{Type: "registry", Version: "2.1"}); versions[1] != check {
   192  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[1], check)
   193  	}
   194  	if check := (APIVersion{Type: "trust", Version: "1.0"}); versions[2] != check {
   195  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[2], check)
   196  	}
   197  	transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, nil, repo2, "pull", "push")))
   198  	client2 := &http.Client{Transport: transport2}
   199  
   200  	req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
   201  	resp, err = client2.Do(req)
   202  	if err != nil {
   203  		t.Fatalf("Error sending get request: %s", err)
   204  	}
   205  
   206  	if resp.StatusCode != http.StatusUnauthorized {
   207  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
   208  	}
   209  }
   210  
   211  func TestEndpointAuthorizeRefreshToken(t *testing.T) {
   212  	service := "localhost.localdomain"
   213  	repo1 := "some/registry"
   214  	repo2 := "other/registry"
   215  	scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
   216  	scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
   217  	refreshToken1 := "0123456790abcdef"
   218  	refreshToken2 := "0123456790fedcba"
   219  	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   220  		{
   221  			Request: testutil.Request{
   222  				Method: "POST",
   223  				Route:  "/token",
   224  				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
   225  			},
   226  			Response: testutil.Response{
   227  				StatusCode: http.StatusOK,
   228  				Body:       []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
   229  			},
   230  		},
   231  		{
   232  			// In the future this test may fail and require using basic auth to get a different refresh token
   233  			Request: testutil.Request{
   234  				Method: "POST",
   235  				Route:  "/token",
   236  				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope2), service)),
   237  			},
   238  			Response: testutil.Response{
   239  				StatusCode: http.StatusOK,
   240  				Body:       []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken2)),
   241  			},
   242  		},
   243  		{
   244  			Request: testutil.Request{
   245  				Method: "POST",
   246  				Route:  "/token",
   247  				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken2, url.QueryEscape(scope2), service)),
   248  			},
   249  			Response: testutil.Response{
   250  				StatusCode: http.StatusOK,
   251  				Body:       []byte(`{"access_token":"badtoken","refresh_token":"%s"}`),
   252  			},
   253  		},
   254  	})
   255  	te, tc := testServer(tokenMap)
   256  	defer tc()
   257  
   258  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   259  		{
   260  			Request: testutil.Request{
   261  				Method: "GET",
   262  				Route:  "/v2/hello",
   263  			},
   264  			Response: testutil.Response{
   265  				StatusCode: http.StatusAccepted,
   266  			},
   267  		},
   268  	})
   269  
   270  	authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
   271  	validCheck := func(a string) bool {
   272  		return a == "Bearer statictoken"
   273  	}
   274  	e, c := testServerWithAuth(m, authenicate, validCheck)
   275  	defer c()
   276  
   277  	challengeManager1 := challenge.NewSimpleManager()
   278  	versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
   279  	if err != nil {
   280  		t.Fatal(err)
   281  	}
   282  	if len(versions) != 1 {
   283  		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
   284  	}
   285  	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
   286  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
   287  	}
   288  	creds := &testCredentialStore{
   289  		refreshTokens: map[string]string{
   290  			service: refreshToken1,
   291  		},
   292  	}
   293  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, creds, repo1, "pull", "push")))
   294  	client := &http.Client{Transport: transport1}
   295  
   296  	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   297  	resp, err := client.Do(req)
   298  	if err != nil {
   299  		t.Fatalf("Error sending get request: %s", err)
   300  	}
   301  
   302  	if resp.StatusCode != http.StatusAccepted {
   303  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   304  	}
   305  
   306  	// Try with refresh token setting
   307  	e2, c2 := testServerWithAuth(m, authenicate, validCheck)
   308  	defer c2()
   309  
   310  	challengeManager2 := challenge.NewSimpleManager()
   311  	versions, err = ping(challengeManager2, e2+"/v2/", "x-api-version")
   312  	if err != nil {
   313  		t.Fatal(err)
   314  	}
   315  	if len(versions) != 1 {
   316  		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
   317  	}
   318  	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
   319  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
   320  	}
   321  
   322  	transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, creds, repo2, "pull", "push")))
   323  	client2 := &http.Client{Transport: transport2}
   324  
   325  	req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
   326  	resp, err = client2.Do(req)
   327  	if err != nil {
   328  		t.Fatalf("Error sending get request: %s", err)
   329  	}
   330  
   331  	if resp.StatusCode != http.StatusAccepted {
   332  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
   333  	}
   334  
   335  	if creds.refreshTokens[service] != refreshToken2 {
   336  		t.Fatalf("Refresh token not set after change")
   337  	}
   338  
   339  	// Try with bad token
   340  	e3, c3 := testServerWithAuth(m, authenicate, validCheck)
   341  	defer c3()
   342  
   343  	challengeManager3 := challenge.NewSimpleManager()
   344  	versions, err = ping(challengeManager3, e3+"/v2/", "x-api-version")
   345  	if err != nil {
   346  		t.Fatal(err)
   347  	}
   348  	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
   349  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
   350  	}
   351  
   352  	transport3 := transport.NewTransport(nil, NewAuthorizer(challengeManager3, NewTokenHandler(nil, creds, repo2, "pull", "push")))
   353  	client3 := &http.Client{Transport: transport3}
   354  
   355  	req, _ = http.NewRequest("GET", e3+"/v2/hello", nil)
   356  	resp, err = client3.Do(req)
   357  	if err != nil {
   358  		t.Fatalf("Error sending get request: %s", err)
   359  	}
   360  
   361  	if resp.StatusCode != http.StatusUnauthorized {
   362  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
   363  	}
   364  }
   365  
   366  func TestEndpointAuthorizeV2RefreshToken(t *testing.T) {
   367  	service := "localhost.localdomain"
   368  	scope1 := "registry:catalog:search"
   369  	refreshToken1 := "0123456790abcdef"
   370  	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   371  		{
   372  			Request: testutil.Request{
   373  				Method: "POST",
   374  				Route:  "/token",
   375  				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
   376  			},
   377  			Response: testutil.Response{
   378  				StatusCode: http.StatusOK,
   379  				Body:       []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
   380  			},
   381  		},
   382  	})
   383  	te, tc := testServer(tokenMap)
   384  	defer tc()
   385  
   386  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   387  		{
   388  			Request: testutil.Request{
   389  				Method: "GET",
   390  				Route:  "/v1/search",
   391  			},
   392  			Response: testutil.Response{
   393  				StatusCode: http.StatusAccepted,
   394  			},
   395  		},
   396  	})
   397  
   398  	authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
   399  	validCheck := func(a string) bool {
   400  		return a == "Bearer statictoken"
   401  	}
   402  	e, c := testServerWithAuth(m, authenicate, validCheck)
   403  	defer c()
   404  
   405  	challengeManager1 := challenge.NewSimpleManager()
   406  	versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
   407  	if err != nil {
   408  		t.Fatal(err)
   409  	}
   410  	if len(versions) != 1 {
   411  		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
   412  	}
   413  	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
   414  		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
   415  	}
   416  	tho := TokenHandlerOptions{
   417  		Credentials: &testCredentialStore{
   418  			refreshTokens: map[string]string{
   419  				service: refreshToken1,
   420  			},
   421  		},
   422  		Scopes: []Scope{
   423  			RegistryScope{
   424  				Name:    "catalog",
   425  				Actions: []string{"search"},
   426  			},
   427  		},
   428  	}
   429  
   430  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandlerWithOptions(tho)))
   431  	client := &http.Client{Transport: transport1}
   432  
   433  	req, _ := http.NewRequest("GET", e+"/v1/search", nil)
   434  	resp, err := client.Do(req)
   435  	if err != nil {
   436  		t.Fatalf("Error sending get request: %s", err)
   437  	}
   438  
   439  	if resp.StatusCode != http.StatusAccepted {
   440  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   441  	}
   442  }
   443  
   444  func basicAuth(username, password string) string {
   445  	auth := username + ":" + password
   446  	return base64.StdEncoding.EncodeToString([]byte(auth))
   447  }
   448  
   449  func TestEndpointAuthorizeTokenBasic(t *testing.T) {
   450  	service := "localhost.localdomain"
   451  	repo := "some/fun/registry"
   452  	scope := fmt.Sprintf("repository:%s:pull,push", repo)
   453  	username := "tokenuser"
   454  	password := "superSecretPa$$word"
   455  
   456  	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   457  		{
   458  			Request: testutil.Request{
   459  				Method: "GET",
   460  				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
   461  			},
   462  			Response: testutil.Response{
   463  				StatusCode: http.StatusOK,
   464  				Body:       []byte(`{"access_token":"statictoken"}`),
   465  			},
   466  		},
   467  	})
   468  
   469  	authenicate1 := "Basic realm=localhost"
   470  	basicCheck := func(a string) bool {
   471  		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
   472  	}
   473  	te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
   474  	defer tc()
   475  
   476  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   477  		{
   478  			Request: testutil.Request{
   479  				Method: "GET",
   480  				Route:  "/v2/hello",
   481  			},
   482  			Response: testutil.Response{
   483  				StatusCode: http.StatusAccepted,
   484  			},
   485  		},
   486  	})
   487  
   488  	authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
   489  	bearerCheck := func(a string) bool {
   490  		return a == "Bearer statictoken"
   491  	}
   492  	e, c := testServerWithAuth(m, authenicate2, bearerCheck)
   493  	defer c()
   494  
   495  	creds := &testCredentialStore{
   496  		username: username,
   497  		password: password,
   498  	}
   499  
   500  	challengeManager := challenge.NewSimpleManager()
   501  	_, err := ping(challengeManager, e+"/v2/", "")
   502  	if err != nil {
   503  		t.Fatal(err)
   504  	}
   505  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewTokenHandler(nil, creds, repo, "pull", "push"), NewBasicHandler(creds)))
   506  	client := &http.Client{Transport: transport1}
   507  
   508  	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   509  	resp, err := client.Do(req)
   510  	if err != nil {
   511  		t.Fatalf("Error sending get request: %s", err)
   512  	}
   513  
   514  	if resp.StatusCode != http.StatusAccepted {
   515  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   516  	}
   517  }
   518  
   519  func TestEndpointAuthorizeTokenBasicWithExpiresIn(t *testing.T) {
   520  	service := "localhost.localdomain"
   521  	repo := "some/fun/registry"
   522  	scope := fmt.Sprintf("repository:%s:pull,push", repo)
   523  	username := "tokenuser"
   524  	password := "superSecretPa$$word"
   525  
   526  	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   527  		{
   528  			Request: testutil.Request{
   529  				Method: "GET",
   530  				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
   531  			},
   532  			Response: testutil.Response{
   533  				StatusCode: http.StatusOK,
   534  				Body:       []byte(`{"token":"statictoken", "expires_in": 3001}`),
   535  			},
   536  		},
   537  		{
   538  			Request: testutil.Request{
   539  				Method: "GET",
   540  				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
   541  			},
   542  			Response: testutil.Response{
   543  				StatusCode: http.StatusOK,
   544  				Body:       []byte(`{"access_token":"statictoken", "expires_in": 3001}`),
   545  			},
   546  		},
   547  	})
   548  
   549  	authenicate1 := "Basic realm=localhost"
   550  	tokenExchanges := 0
   551  	basicCheck := func(a string) bool {
   552  		tokenExchanges = tokenExchanges + 1
   553  		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
   554  	}
   555  	te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
   556  	defer tc()
   557  
   558  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   559  		{
   560  			Request: testutil.Request{
   561  				Method: "GET",
   562  				Route:  "/v2/hello",
   563  			},
   564  			Response: testutil.Response{
   565  				StatusCode: http.StatusAccepted,
   566  			},
   567  		},
   568  		{
   569  			Request: testutil.Request{
   570  				Method: "GET",
   571  				Route:  "/v2/hello",
   572  			},
   573  			Response: testutil.Response{
   574  				StatusCode: http.StatusAccepted,
   575  			},
   576  		},
   577  		{
   578  			Request: testutil.Request{
   579  				Method: "GET",
   580  				Route:  "/v2/hello",
   581  			},
   582  			Response: testutil.Response{
   583  				StatusCode: http.StatusAccepted,
   584  			},
   585  		},
   586  		{
   587  			Request: testutil.Request{
   588  				Method: "GET",
   589  				Route:  "/v2/hello",
   590  			},
   591  			Response: testutil.Response{
   592  				StatusCode: http.StatusAccepted,
   593  			},
   594  		},
   595  		{
   596  			Request: testutil.Request{
   597  				Method: "GET",
   598  				Route:  "/v2/hello",
   599  			},
   600  			Response: testutil.Response{
   601  				StatusCode: http.StatusAccepted,
   602  			},
   603  		},
   604  	})
   605  
   606  	authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
   607  	bearerCheck := func(a string) bool {
   608  		return a == "Bearer statictoken"
   609  	}
   610  	e, c := testServerWithAuth(m, authenicate2, bearerCheck)
   611  	defer c()
   612  
   613  	creds := &testCredentialStore{
   614  		username: username,
   615  		password: password,
   616  	}
   617  
   618  	challengeManager := challenge.NewSimpleManager()
   619  	_, err := ping(challengeManager, e+"/v2/", "")
   620  	if err != nil {
   621  		t.Fatal(err)
   622  	}
   623  	clock := &fakeClock{current: time.Now()}
   624  	options := TokenHandlerOptions{
   625  		Transport:   nil,
   626  		Credentials: creds,
   627  		Scopes: []Scope{
   628  			RepositoryScope{
   629  				Repository: repo,
   630  				Actions:    []string{"pull", "push"},
   631  			},
   632  		},
   633  	}
   634  	tHandler := NewTokenHandlerWithOptions(options)
   635  	tHandler.(*tokenHandler).clock = clock
   636  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds)))
   637  	client := &http.Client{Transport: transport1}
   638  
   639  	// First call should result in a token exchange
   640  	// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
   641  	timeIncrement := 1000 * time.Second
   642  	for i := 0; i < 4; i++ {
   643  		req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   644  		resp, err := client.Do(req)
   645  		if err != nil {
   646  			t.Fatalf("Error sending get request: %s", err)
   647  		}
   648  		if resp.StatusCode != http.StatusAccepted {
   649  			t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   650  		}
   651  		if tokenExchanges != 1 {
   652  			t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
   653  		}
   654  		clock.current = clock.current.Add(timeIncrement)
   655  	}
   656  
   657  	// After we've exceeded the expiration, we should see a second token exchange.
   658  	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   659  	resp, err := client.Do(req)
   660  	if err != nil {
   661  		t.Fatalf("Error sending get request: %s", err)
   662  	}
   663  	if resp.StatusCode != http.StatusAccepted {
   664  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   665  	}
   666  	if tokenExchanges != 2 {
   667  		t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
   668  	}
   669  }
   670  
   671  func TestEndpointAuthorizeTokenBasicWithExpiresInAndIssuedAt(t *testing.T) {
   672  	service := "localhost.localdomain"
   673  	repo := "some/fun/registry"
   674  	scope := fmt.Sprintf("repository:%s:pull,push", repo)
   675  	username := "tokenuser"
   676  	password := "superSecretPa$$word"
   677  
   678  	// This test sets things up such that the token was issued one increment
   679  	// earlier than its sibling in TestEndpointAuthorizeTokenBasicWithExpiresIn.
   680  	// This will mean that the token expires after 3 increments instead of 4.
   681  	clock := &fakeClock{current: time.Now()}
   682  	timeIncrement := 1000 * time.Second
   683  	firstIssuedAt := clock.Now()
   684  	clock.current = clock.current.Add(timeIncrement)
   685  	secondIssuedAt := clock.current.Add(2 * timeIncrement)
   686  	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   687  		{
   688  			Request: testutil.Request{
   689  				Method: "GET",
   690  				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
   691  			},
   692  			Response: testutil.Response{
   693  				StatusCode: http.StatusOK,
   694  				Body:       []byte(`{"token":"statictoken", "issued_at": "` + firstIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
   695  			},
   696  		},
   697  		{
   698  			Request: testutil.Request{
   699  				Method: "GET",
   700  				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
   701  			},
   702  			Response: testutil.Response{
   703  				StatusCode: http.StatusOK,
   704  				Body:       []byte(`{"access_token":"statictoken", "issued_at": "` + secondIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
   705  			},
   706  		},
   707  	})
   708  
   709  	authenicate1 := "Basic realm=localhost"
   710  	tokenExchanges := 0
   711  	basicCheck := func(a string) bool {
   712  		tokenExchanges = tokenExchanges + 1
   713  		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
   714  	}
   715  	te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
   716  	defer tc()
   717  
   718  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   719  		{
   720  			Request: testutil.Request{
   721  				Method: "GET",
   722  				Route:  "/v2/hello",
   723  			},
   724  			Response: testutil.Response{
   725  				StatusCode: http.StatusAccepted,
   726  			},
   727  		},
   728  		{
   729  			Request: testutil.Request{
   730  				Method: "GET",
   731  				Route:  "/v2/hello",
   732  			},
   733  			Response: testutil.Response{
   734  				StatusCode: http.StatusAccepted,
   735  			},
   736  		},
   737  		{
   738  			Request: testutil.Request{
   739  				Method: "GET",
   740  				Route:  "/v2/hello",
   741  			},
   742  			Response: testutil.Response{
   743  				StatusCode: http.StatusAccepted,
   744  			},
   745  		},
   746  		{
   747  			Request: testutil.Request{
   748  				Method: "GET",
   749  				Route:  "/v2/hello",
   750  			},
   751  			Response: testutil.Response{
   752  				StatusCode: http.StatusAccepted,
   753  			},
   754  		},
   755  	})
   756  
   757  	authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
   758  	bearerCheck := func(a string) bool {
   759  		return a == "Bearer statictoken"
   760  	}
   761  	e, c := testServerWithAuth(m, authenicate2, bearerCheck)
   762  	defer c()
   763  
   764  	creds := &testCredentialStore{
   765  		username: username,
   766  		password: password,
   767  	}
   768  
   769  	challengeManager := challenge.NewSimpleManager()
   770  	_, err := ping(challengeManager, e+"/v2/", "")
   771  	if err != nil {
   772  		t.Fatal(err)
   773  	}
   774  
   775  	options := TokenHandlerOptions{
   776  		Transport:   nil,
   777  		Credentials: creds,
   778  		Scopes: []Scope{
   779  			RepositoryScope{
   780  				Repository: repo,
   781  				Actions:    []string{"pull", "push"},
   782  			},
   783  		},
   784  	}
   785  	tHandler := NewTokenHandlerWithOptions(options)
   786  	tHandler.(*tokenHandler).clock = clock
   787  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds)))
   788  	client := &http.Client{Transport: transport1}
   789  
   790  	// First call should result in a token exchange
   791  	// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
   792  	// We shaved one increment off of the equivalent logic in TestEndpointAuthorizeTokenBasicWithExpiresIn
   793  	// so this loop should have one fewer iteration.
   794  	for i := 0; i < 3; i++ {
   795  		req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   796  		resp, err := client.Do(req)
   797  		if err != nil {
   798  			t.Fatalf("Error sending get request: %s", err)
   799  		}
   800  		if resp.StatusCode != http.StatusAccepted {
   801  			t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   802  		}
   803  		if tokenExchanges != 1 {
   804  			t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
   805  		}
   806  		clock.current = clock.current.Add(timeIncrement)
   807  	}
   808  
   809  	// After we've exceeded the expiration, we should see a second token exchange.
   810  	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   811  	resp, err := client.Do(req)
   812  	if err != nil {
   813  		t.Fatalf("Error sending get request: %s", err)
   814  	}
   815  	if resp.StatusCode != http.StatusAccepted {
   816  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   817  	}
   818  	if tokenExchanges != 2 {
   819  		t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
   820  	}
   821  }
   822  
   823  func TestEndpointAuthorizeBasic(t *testing.T) {
   824  	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
   825  		{
   826  			Request: testutil.Request{
   827  				Method: "GET",
   828  				Route:  "/v2/hello",
   829  			},
   830  			Response: testutil.Response{
   831  				StatusCode: http.StatusAccepted,
   832  			},
   833  		},
   834  	})
   835  
   836  	username := "user1"
   837  	password := "funSecretPa$$word"
   838  	authenicate := "Basic realm=localhost"
   839  	validCheck := func(a string) bool {
   840  		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
   841  	}
   842  	e, c := testServerWithAuth(m, authenicate, validCheck)
   843  	defer c()
   844  	creds := &testCredentialStore{
   845  		username: username,
   846  		password: password,
   847  	}
   848  
   849  	challengeManager := challenge.NewSimpleManager()
   850  	_, err := ping(challengeManager, e+"/v2/", "")
   851  	if err != nil {
   852  		t.Fatal(err)
   853  	}
   854  	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewBasicHandler(creds)))
   855  	client := &http.Client{Transport: transport1}
   856  
   857  	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
   858  	resp, err := client.Do(req)
   859  	if err != nil {
   860  		t.Fatalf("Error sending get request: %s", err)
   861  	}
   862  
   863  	if resp.StatusCode != http.StatusAccepted {
   864  		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
   865  	}
   866  }
   867  

View as plain text