867 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
			
		
		
	
	
			867 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
package auth
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/base64"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"net/http/httptest"
 | 
						|
	"net/url"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/docker/distribution/registry/client/auth/challenge"
 | 
						|
	"github.com/docker/distribution/registry/client/transport"
 | 
						|
	"github.com/docker/distribution/testutil"
 | 
						|
)
 | 
						|
 | 
						|
// An implementation of clock for providing fake time data.
 | 
						|
type fakeClock struct {
 | 
						|
	current time.Time
 | 
						|
}
 | 
						|
 | 
						|
// Now implements clock
 | 
						|
func (fc *fakeClock) Now() time.Time { return fc.current }
 | 
						|
 | 
						|
func testServer(rrm testutil.RequestResponseMap) (string, func()) {
 | 
						|
	h := testutil.NewHandler(rrm)
 | 
						|
	s := httptest.NewServer(h)
 | 
						|
	return s.URL, s.Close
 | 
						|
}
 | 
						|
 | 
						|
type testAuthenticationWrapper struct {
 | 
						|
	headers   http.Header
 | 
						|
	authCheck func(string) bool
 | 
						|
	next      http.Handler
 | 
						|
}
 | 
						|
 | 
						|
func (w *testAuthenticationWrapper) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
 | 
						|
	auth := r.Header.Get("Authorization")
 | 
						|
	if auth == "" || !w.authCheck(auth) {
 | 
						|
		h := rw.Header()
 | 
						|
		for k, values := range w.headers {
 | 
						|
			h[k] = values
 | 
						|
		}
 | 
						|
		rw.WriteHeader(http.StatusUnauthorized)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	w.next.ServeHTTP(rw, r)
 | 
						|
}
 | 
						|
 | 
						|
func testServerWithAuth(rrm testutil.RequestResponseMap, authenticate string, authCheck func(string) bool) (string, func()) {
 | 
						|
	h := testutil.NewHandler(rrm)
 | 
						|
	wrapper := &testAuthenticationWrapper{
 | 
						|
 | 
						|
		headers: http.Header(map[string][]string{
 | 
						|
			"X-API-Version":       {"registry/2.0"},
 | 
						|
			"X-Multi-API-Version": {"registry/2.0", "registry/2.1", "trust/1.0"},
 | 
						|
			"WWW-Authenticate":    {authenticate},
 | 
						|
		}),
 | 
						|
		authCheck: authCheck,
 | 
						|
		next:      h,
 | 
						|
	}
 | 
						|
 | 
						|
	s := httptest.NewServer(wrapper)
 | 
						|
	return s.URL, s.Close
 | 
						|
}
 | 
						|
 | 
						|
// ping pings the provided endpoint to determine its required authorization challenges.
 | 
						|
// If a version header is provided, the versions will be returned.
 | 
						|
func ping(manager challenge.Manager, endpoint, versionHeader string) ([]APIVersion, error) {
 | 
						|
	resp, err := http.Get(endpoint)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer resp.Body.Close()
 | 
						|
 | 
						|
	if err := manager.AddResponse(resp); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return APIVersions(resp, versionHeader), err
 | 
						|
}
 | 
						|
 | 
						|
type testCredentialStore struct {
 | 
						|
	username      string
 | 
						|
	password      string
 | 
						|
	refreshTokens map[string]string
 | 
						|
}
 | 
						|
 | 
						|
func (tcs *testCredentialStore) Basic(*url.URL) (string, string) {
 | 
						|
	return tcs.username, tcs.password
 | 
						|
}
 | 
						|
 | 
						|
func (tcs *testCredentialStore) RefreshToken(u *url.URL, service string) string {
 | 
						|
	return tcs.refreshTokens[service]
 | 
						|
}
 | 
						|
 | 
						|
func (tcs *testCredentialStore) SetRefreshToken(u *url.URL, service string, token string) {
 | 
						|
	if tcs.refreshTokens != nil {
 | 
						|
		tcs.refreshTokens[service] = token
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeToken(t *testing.T) {
 | 
						|
	service := "localhost.localdomain"
 | 
						|
	repo1 := "some/registry"
 | 
						|
	repo2 := "other/registry"
 | 
						|
	scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
 | 
						|
	scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
 | 
						|
	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?scope=%s&service=%s", url.QueryEscape(scope1), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"token":"statictoken"}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?scope=%s&service=%s", url.QueryEscape(scope2), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"token":"badtoken"}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
	te, tc := testServer(tokenMap)
 | 
						|
	defer tc()
 | 
						|
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
 | 
						|
	validCheck := func(a string) bool {
 | 
						|
		return a == "Bearer statictoken"
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c()
 | 
						|
 | 
						|
	challengeManager1 := challenge.NewSimpleManager()
 | 
						|
	versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(versions) != 1 {
 | 
						|
		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
 | 
						|
	}
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, nil, repo1, "pull", "push")))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
 | 
						|
	e2, c2 := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c2()
 | 
						|
 | 
						|
	challengeManager2 := challenge.NewSimpleManager()
 | 
						|
	versions, err = ping(challengeManager2, e2+"/v2/", "x-multi-api-version")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(versions) != 3 {
 | 
						|
		t.Fatalf("Unexpected version count: %d, expected 3", len(versions))
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.1"}); versions[1] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[1], check)
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "trust", Version: "1.0"}); versions[2] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[2], check)
 | 
						|
	}
 | 
						|
	transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, nil, repo2, "pull", "push")))
 | 
						|
	client2 := &http.Client{Transport: transport2}
 | 
						|
 | 
						|
	req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
 | 
						|
	resp, err = client2.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusUnauthorized {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeRefreshToken(t *testing.T) {
 | 
						|
	service := "localhost.localdomain"
 | 
						|
	repo1 := "some/registry"
 | 
						|
	repo2 := "other/registry"
 | 
						|
	scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
 | 
						|
	scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
 | 
						|
	refreshToken1 := "0123456790abcdef"
 | 
						|
	refreshToken2 := "0123456790fedcba"
 | 
						|
	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "POST",
 | 
						|
				Route:  "/token",
 | 
						|
				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			// In the future this test may fail and require using basic auth to get a different refresh token
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "POST",
 | 
						|
				Route:  "/token",
 | 
						|
				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope2), service)),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken2)),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "POST",
 | 
						|
				Route:  "/token",
 | 
						|
				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken2, url.QueryEscape(scope2), service)),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"access_token":"badtoken","refresh_token":"%s"}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
	te, tc := testServer(tokenMap)
 | 
						|
	defer tc()
 | 
						|
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
 | 
						|
	validCheck := func(a string) bool {
 | 
						|
		return a == "Bearer statictoken"
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c()
 | 
						|
 | 
						|
	challengeManager1 := challenge.NewSimpleManager()
 | 
						|
	versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(versions) != 1 {
 | 
						|
		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
 | 
						|
	}
 | 
						|
	creds := &testCredentialStore{
 | 
						|
		refreshTokens: map[string]string{
 | 
						|
			service: refreshToken1,
 | 
						|
		},
 | 
						|
	}
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, creds, repo1, "pull", "push")))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
 | 
						|
	// Try with refresh token setting
 | 
						|
	e2, c2 := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c2()
 | 
						|
 | 
						|
	challengeManager2 := challenge.NewSimpleManager()
 | 
						|
	versions, err = ping(challengeManager2, e2+"/v2/", "x-api-version")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(versions) != 1 {
 | 
						|
		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
 | 
						|
	}
 | 
						|
 | 
						|
	transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, creds, repo2, "pull", "push")))
 | 
						|
	client2 := &http.Client{Transport: transport2}
 | 
						|
 | 
						|
	req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
 | 
						|
	resp, err = client2.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
 | 
						|
	}
 | 
						|
 | 
						|
	if creds.refreshTokens[service] != refreshToken2 {
 | 
						|
		t.Fatalf("Refresh token not set after change")
 | 
						|
	}
 | 
						|
 | 
						|
	// Try with bad token
 | 
						|
	e3, c3 := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c3()
 | 
						|
 | 
						|
	challengeManager3 := challenge.NewSimpleManager()
 | 
						|
	versions, err = ping(challengeManager3, e3+"/v2/", "x-api-version")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
 | 
						|
	}
 | 
						|
 | 
						|
	transport3 := transport.NewTransport(nil, NewAuthorizer(challengeManager3, NewTokenHandler(nil, creds, repo2, "pull", "push")))
 | 
						|
	client3 := &http.Client{Transport: transport3}
 | 
						|
 | 
						|
	req, _ = http.NewRequest("GET", e3+"/v2/hello", nil)
 | 
						|
	resp, err = client3.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusUnauthorized {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeV2RefreshToken(t *testing.T) {
 | 
						|
	service := "localhost.localdomain"
 | 
						|
	scope1 := "registry:catalog:search"
 | 
						|
	refreshToken1 := "0123456790abcdef"
 | 
						|
	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "POST",
 | 
						|
				Route:  "/token",
 | 
						|
				Body:   []byte(fmt.Sprintf("client_id=registry-client&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
	te, tc := testServer(tokenMap)
 | 
						|
	defer tc()
 | 
						|
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v1/search",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
 | 
						|
	validCheck := func(a string) bool {
 | 
						|
		return a == "Bearer statictoken"
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c()
 | 
						|
 | 
						|
	challengeManager1 := challenge.NewSimpleManager()
 | 
						|
	versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	if len(versions) != 1 {
 | 
						|
		t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
 | 
						|
	}
 | 
						|
	if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
 | 
						|
		t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
 | 
						|
	}
 | 
						|
	tho := TokenHandlerOptions{
 | 
						|
		Credentials: &testCredentialStore{
 | 
						|
			refreshTokens: map[string]string{
 | 
						|
				service: refreshToken1,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		Scopes: []Scope{
 | 
						|
			RegistryScope{
 | 
						|
				Name:    "catalog",
 | 
						|
				Actions: []string{"search"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandlerWithOptions(tho)))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v1/search", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func basicAuth(username, password string) string {
 | 
						|
	auth := username + ":" + password
 | 
						|
	return base64.StdEncoding.EncodeToString([]byte(auth))
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeTokenBasic(t *testing.T) {
 | 
						|
	service := "localhost.localdomain"
 | 
						|
	repo := "some/fun/registry"
 | 
						|
	scope := fmt.Sprintf("repository:%s:pull,push", repo)
 | 
						|
	username := "tokenuser"
 | 
						|
	password := "superSecretPa$$word"
 | 
						|
 | 
						|
	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"access_token":"statictoken"}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate1 := "Basic realm=localhost"
 | 
						|
	basicCheck := func(a string) bool {
 | 
						|
		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
 | 
						|
	}
 | 
						|
	te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
 | 
						|
	defer tc()
 | 
						|
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
 | 
						|
	bearerCheck := func(a string) bool {
 | 
						|
		return a == "Bearer statictoken"
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate2, bearerCheck)
 | 
						|
	defer c()
 | 
						|
 | 
						|
	creds := &testCredentialStore{
 | 
						|
		username: username,
 | 
						|
		password: password,
 | 
						|
	}
 | 
						|
 | 
						|
	challengeManager := challenge.NewSimpleManager()
 | 
						|
	_, err := ping(challengeManager, e+"/v2/", "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewTokenHandler(nil, creds, repo, "pull", "push"), NewBasicHandler(creds)))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeTokenBasicWithExpiresIn(t *testing.T) {
 | 
						|
	service := "localhost.localdomain"
 | 
						|
	repo := "some/fun/registry"
 | 
						|
	scope := fmt.Sprintf("repository:%s:pull,push", repo)
 | 
						|
	username := "tokenuser"
 | 
						|
	password := "superSecretPa$$word"
 | 
						|
 | 
						|
	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"token":"statictoken", "expires_in": 3001}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"access_token":"statictoken", "expires_in": 3001}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate1 := "Basic realm=localhost"
 | 
						|
	tokenExchanges := 0
 | 
						|
	basicCheck := func(a string) bool {
 | 
						|
		tokenExchanges = tokenExchanges + 1
 | 
						|
		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
 | 
						|
	}
 | 
						|
	te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
 | 
						|
	defer tc()
 | 
						|
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
 | 
						|
	bearerCheck := func(a string) bool {
 | 
						|
		return a == "Bearer statictoken"
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate2, bearerCheck)
 | 
						|
	defer c()
 | 
						|
 | 
						|
	creds := &testCredentialStore{
 | 
						|
		username: username,
 | 
						|
		password: password,
 | 
						|
	}
 | 
						|
 | 
						|
	challengeManager := challenge.NewSimpleManager()
 | 
						|
	_, err := ping(challengeManager, e+"/v2/", "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	clock := &fakeClock{current: time.Now()}
 | 
						|
	options := TokenHandlerOptions{
 | 
						|
		Transport:   nil,
 | 
						|
		Credentials: creds,
 | 
						|
		Scopes: []Scope{
 | 
						|
			RepositoryScope{
 | 
						|
				Repository: repo,
 | 
						|
				Actions:    []string{"pull", "push"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	tHandler := NewTokenHandlerWithOptions(options)
 | 
						|
	tHandler.(*tokenHandler).clock = clock
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds)))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	// First call should result in a token exchange
 | 
						|
	// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
 | 
						|
	timeIncrement := 1000 * time.Second
 | 
						|
	for i := 0; i < 4; i++ {
 | 
						|
		req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
		resp, err := client.Do(req)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Error sending get request: %s", err)
 | 
						|
		}
 | 
						|
		if resp.StatusCode != http.StatusAccepted {
 | 
						|
			t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
		}
 | 
						|
		if tokenExchanges != 1 {
 | 
						|
			t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
 | 
						|
		}
 | 
						|
		clock.current = clock.current.Add(timeIncrement)
 | 
						|
	}
 | 
						|
 | 
						|
	// After we've exceeded the expiration, we should see a second token exchange.
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
	if tokenExchanges != 2 {
 | 
						|
		t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeTokenBasicWithExpiresInAndIssuedAt(t *testing.T) {
 | 
						|
	service := "localhost.localdomain"
 | 
						|
	repo := "some/fun/registry"
 | 
						|
	scope := fmt.Sprintf("repository:%s:pull,push", repo)
 | 
						|
	username := "tokenuser"
 | 
						|
	password := "superSecretPa$$word"
 | 
						|
 | 
						|
	// This test sets things up such that the token was issued one increment
 | 
						|
	// earlier than its sibling in TestEndpointAuthorizeTokenBasicWithExpiresIn.
 | 
						|
	// This will mean that the token expires after 3 increments instead of 4.
 | 
						|
	clock := &fakeClock{current: time.Now()}
 | 
						|
	timeIncrement := 1000 * time.Second
 | 
						|
	firstIssuedAt := clock.Now()
 | 
						|
	clock.current = clock.current.Add(timeIncrement)
 | 
						|
	secondIssuedAt := clock.current.Add(2 * timeIncrement)
 | 
						|
	tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"token":"statictoken", "issued_at": "` + firstIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  fmt.Sprintf("/token?account=%s&scope=%s&service=%s", username, url.QueryEscape(scope), service),
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusOK,
 | 
						|
				Body:       []byte(`{"access_token":"statictoken", "issued_at": "` + secondIssuedAt.Format(time.RFC3339Nano) + `", "expires_in": 3001}`),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate1 := "Basic realm=localhost"
 | 
						|
	tokenExchanges := 0
 | 
						|
	basicCheck := func(a string) bool {
 | 
						|
		tokenExchanges = tokenExchanges + 1
 | 
						|
		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
 | 
						|
	}
 | 
						|
	te, tc := testServerWithAuth(tokenMap, authenicate1, basicCheck)
 | 
						|
	defer tc()
 | 
						|
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	authenicate2 := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
 | 
						|
	bearerCheck := func(a string) bool {
 | 
						|
		return a == "Bearer statictoken"
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate2, bearerCheck)
 | 
						|
	defer c()
 | 
						|
 | 
						|
	creds := &testCredentialStore{
 | 
						|
		username: username,
 | 
						|
		password: password,
 | 
						|
	}
 | 
						|
 | 
						|
	challengeManager := challenge.NewSimpleManager()
 | 
						|
	_, err := ping(challengeManager, e+"/v2/", "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	options := TokenHandlerOptions{
 | 
						|
		Transport:   nil,
 | 
						|
		Credentials: creds,
 | 
						|
		Scopes: []Scope{
 | 
						|
			RepositoryScope{
 | 
						|
				Repository: repo,
 | 
						|
				Actions:    []string{"pull", "push"},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	tHandler := NewTokenHandlerWithOptions(options)
 | 
						|
	tHandler.(*tokenHandler).clock = clock
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, tHandler, NewBasicHandler(creds)))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	// First call should result in a token exchange
 | 
						|
	// Subsequent calls should recycle the token from the first request, until the expiration has lapsed.
 | 
						|
	// We shaved one increment off of the equivalent logic in TestEndpointAuthorizeTokenBasicWithExpiresIn
 | 
						|
	// so this loop should have one fewer iteration.
 | 
						|
	for i := 0; i < 3; i++ {
 | 
						|
		req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
		resp, err := client.Do(req)
 | 
						|
		if err != nil {
 | 
						|
			t.Fatalf("Error sending get request: %s", err)
 | 
						|
		}
 | 
						|
		if resp.StatusCode != http.StatusAccepted {
 | 
						|
			t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
		}
 | 
						|
		if tokenExchanges != 1 {
 | 
						|
			t.Fatalf("Unexpected number of token exchanges, want: 1, got %d (iteration: %d)", tokenExchanges, i)
 | 
						|
		}
 | 
						|
		clock.current = clock.current.Add(timeIncrement)
 | 
						|
	}
 | 
						|
 | 
						|
	// After we've exceeded the expiration, we should see a second token exchange.
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
	if tokenExchanges != 2 {
 | 
						|
		t.Fatalf("Unexpected number of token exchanges, want: 2, got %d", tokenExchanges)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func TestEndpointAuthorizeBasic(t *testing.T) {
 | 
						|
	m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
 | 
						|
		{
 | 
						|
			Request: testutil.Request{
 | 
						|
				Method: "GET",
 | 
						|
				Route:  "/v2/hello",
 | 
						|
			},
 | 
						|
			Response: testutil.Response{
 | 
						|
				StatusCode: http.StatusAccepted,
 | 
						|
			},
 | 
						|
		},
 | 
						|
	})
 | 
						|
 | 
						|
	username := "user1"
 | 
						|
	password := "funSecretPa$$word"
 | 
						|
	authenicate := "Basic realm=localhost"
 | 
						|
	validCheck := func(a string) bool {
 | 
						|
		return a == fmt.Sprintf("Basic %s", basicAuth(username, password))
 | 
						|
	}
 | 
						|
	e, c := testServerWithAuth(m, authenicate, validCheck)
 | 
						|
	defer c()
 | 
						|
	creds := &testCredentialStore{
 | 
						|
		username: username,
 | 
						|
		password: password,
 | 
						|
	}
 | 
						|
 | 
						|
	challengeManager := challenge.NewSimpleManager()
 | 
						|
	_, err := ping(challengeManager, e+"/v2/", "")
 | 
						|
	if err != nil {
 | 
						|
		t.Fatal(err)
 | 
						|
	}
 | 
						|
	transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager, NewBasicHandler(creds)))
 | 
						|
	client := &http.Client{Transport: transport1}
 | 
						|
 | 
						|
	req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
 | 
						|
	resp, err := client.Do(req)
 | 
						|
	if err != nil {
 | 
						|
		t.Fatalf("Error sending get request: %s", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if resp.StatusCode != http.StatusAccepted {
 | 
						|
		t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
 | 
						|
	}
 | 
						|
}
 |