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)
This commit is contained in:
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
Block a user