forked from TrueCloudLab/distribution
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 {
|
||||
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
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue