Add OAuth error for client
Allow clients to handle errors being set in the WWW-Authenticate rather than in the body. The WWW-Authenticate errors give a more precise error describing what is needed to authorize with the server. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)master
							parent
							
								
									a1a73884f9
								
							
						
					
					
						commit
						16396a7a80
					
				|  | @ -9,6 +9,7 @@ import ( | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 
 | 
 | ||||||
| 	"github.com/docker/distribution/registry/api/errcode" | 	"github.com/docker/distribution/registry/api/errcode" | ||||||
|  | 	"github.com/docker/distribution/registry/client/auth/challenge" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ErrNoErrorsInBody is returned when an HTTP response body parses to an empty
 | // ErrNoErrorsInBody is returned when an HTTP response body parses to an empty
 | ||||||
|  | @ -37,6 +38,25 @@ func (e *UnexpectedHTTPResponseError) Error() string { | ||||||
| 	return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response)) | 	return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // OAuthError is returned when the request could not be authorized
 | ||||||
|  | // using the provided oauth token. This could represent a lack of
 | ||||||
|  | // permission or invalid token given from a token server.
 | ||||||
|  | // See https://tools.ietf.org/html/rfc6750#section-3
 | ||||||
|  | type OAuthError struct { | ||||||
|  | 	// ErrorCode is a code defined in https://tools.ietf.org/html/rfc6750#section-3.1
 | ||||||
|  | 	ErrorCode string | ||||||
|  | 
 | ||||||
|  | 	// Description is the error description associated with the error code
 | ||||||
|  | 	Description string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *OAuthError) Error() string { | ||||||
|  | 	if e.Description != "" { | ||||||
|  | 		return fmt.Sprintf("oauth error %q: %s", e.ErrorCode, e.Description) | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("oauth error %q", e.ErrorCode) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func parseHTTPErrorResponse(statusCode int, r io.Reader) error { | func parseHTTPErrorResponse(statusCode int, r io.Reader) error { | ||||||
| 	var errors errcode.Errors | 	var errors errcode.Errors | ||||||
| 	body, err := ioutil.ReadAll(r) | 	body, err := ioutil.ReadAll(r) | ||||||
|  | @ -87,16 +107,25 @@ func parseHTTPErrorResponse(statusCode int, r io.Reader) error { | ||||||
| // UnexpectedHTTPStatusError returned for response code outside of expected
 | // UnexpectedHTTPStatusError returned for response code outside of expected
 | ||||||
| // range.
 | // range.
 | ||||||
| func HandleErrorResponse(resp *http.Response) error { | func HandleErrorResponse(resp *http.Response) error { | ||||||
| 	if resp.StatusCode == 401 { | 	if resp.StatusCode >= 400 && resp.StatusCode < 500 { | ||||||
|  | 		// Check for OAuth errors within the `WWW-Authenticate` header first
 | ||||||
|  | 		for _, c := range challenge.ResponseChallenges(resp) { | ||||||
|  | 			if c.Scheme == "bearer" { | ||||||
|  | 				errStr := c.Parameters["error"] | ||||||
|  | 				if errStr != "" { | ||||||
|  | 					return &OAuthError{ | ||||||
|  | 						ErrorCode:   errStr, | ||||||
|  | 						Description: c.Parameters["error_description"], | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) | 		err := parseHTTPErrorResponse(resp.StatusCode, resp.Body) | ||||||
| 		if uErr, ok := err.(*UnexpectedHTTPResponseError); ok { | 		if uErr, ok := err.(*UnexpectedHTTPResponseError); ok && resp.StatusCode == 401 { | ||||||
| 			return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) | 			return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) | ||||||
| 		} | 		} | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if resp.StatusCode >= 400 && resp.StatusCode < 500 { |  | ||||||
| 		return parseHTTPErrorResponse(resp.StatusCode, resp.Body) |  | ||||||
| 	} |  | ||||||
| 	return &UnexpectedHTTPStatusError{Status: resp.Status} | 	return &UnexpectedHTTPStatusError{Status: resp.Status} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue