From 145abeea7bb8a6bfe54d9cf05222caf9b8f7b80d Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 12 Jul 2016 17:15:56 -0700 Subject: [PATCH] Add support for using v2 ping challenges for v1 Allows using v2 for v1 endpoints. The primary use case being for search which does not have a v2 specification. Added a user scope for allowing v2 search Signed-off-by: Derek McGowan (github: dmcgowan) --- registry/client/auth/session.go | 23 ++++++-- registry/client/auth/session_test.go | 78 ++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 3 deletions(-) diff --git a/registry/client/auth/session.go b/registry/client/auth/session.go index f3497b17..d03d8ff0 100644 --- a/registry/client/auth/session.go +++ b/registry/client/auth/session.go @@ -72,15 +72,19 @@ type endpointAuthorizer struct { } func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error { - v2Root := strings.Index(req.URL.Path, "/v2/") - if v2Root == -1 { + pingPath := req.URL.Path + if v2Root := strings.Index(req.URL.Path, "/v2/"); v2Root != -1 { + pingPath = pingPath[:v2Root+4] + } else if v1Root := strings.Index(req.URL.Path, "/v1/"); v1Root != -1 { + pingPath = pingPath[:v1Root] + "/v2/" + } else { return nil } ping := url.URL{ Host: req.URL.Host, Scheme: req.URL.Scheme, - Path: req.URL.Path[:v2Root+4], + Path: pingPath, } challenges, err := ea.challenges.GetChallenges(ping) @@ -151,6 +155,19 @@ func (rs RepositoryScope) String() string { return fmt.Sprintf("repository:%s:%s", rs.Repository, strings.Join(rs.Actions, ",")) } +// RegistryScope represents a token scope for access +// to resources in the registry. +type RegistryScope struct { + Name string + Actions []string +} + +// String returns the string representation of the user +// using the scope grammar +func (rs RegistryScope) String() string { + return fmt.Sprintf("registry:%s:%s", rs.Name, strings.Join(rs.Actions, ",")) +} + // TokenHandlerOptions is used to configure a new token handler type TokenHandlerOptions struct { Transport http.RoundTripper diff --git a/registry/client/auth/session_test.go b/registry/client/auth/session_test.go index 96c62990..cfae4f97 100644 --- a/registry/client/auth/session_test.go +++ b/registry/client/auth/session_test.go @@ -362,6 +362,84 @@ func TestEndpointAuthorizeRefreshToken(t *testing.T) { } } +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 := NewSimpleChallengeManager() + 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))