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…
	
	Add table
		Add a link
		
	
		Reference in a new issue