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 <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
4e17ab5d31
commit
145abeea7b
2 changed files with 98 additions and 3 deletions
|
@ -72,15 +72,19 @@ type endpointAuthorizer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
|
func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
|
||||||
v2Root := strings.Index(req.URL.Path, "/v2/")
|
pingPath := req.URL.Path
|
||||||
if v2Root == -1 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ping := url.URL{
|
ping := url.URL{
|
||||||
Host: req.URL.Host,
|
Host: req.URL.Host,
|
||||||
Scheme: req.URL.Scheme,
|
Scheme: req.URL.Scheme,
|
||||||
Path: req.URL.Path[:v2Root+4],
|
Path: pingPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
challenges, err := ea.challenges.GetChallenges(ping)
|
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, ","))
|
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
|
// TokenHandlerOptions is used to configure a new token handler
|
||||||
type TokenHandlerOptions struct {
|
type TokenHandlerOptions struct {
|
||||||
Transport http.RoundTripper
|
Transport http.RoundTripper
|
||||||
|
|
|
@ -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 {
|
func basicAuth(username, password string) string {
|
||||||
auth := username + ":" + password
|
auth := username + ":" + password
|
||||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
|
Loading…
Reference in a new issue