From 16396a7a804cd8cadc59c8d7cfed2c5320028c75 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Wed, 9 Nov 2016 14:57:53 -0800 Subject: [PATCH] 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 (github: dmcgowan) --- registry/client/errors.go | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/registry/client/errors.go b/registry/client/errors.go index f73e3c23..585611bd 100644 --- a/registry/client/errors.go +++ b/registry/client/errors.go @@ -9,6 +9,7 @@ import ( "net/http" "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 @@ -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)) } +// 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 { var errors errcode.Errors 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 // range. 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) - if uErr, ok := err.(*UnexpectedHTTPResponseError); ok { + if uErr, ok := err.(*UnexpectedHTTPResponseError); ok && resp.StatusCode == 401 { return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response) } return err } - if resp.StatusCode >= 400 && resp.StatusCode < 500 { - return parseHTTPErrorResponse(resp.StatusCode, resp.Body) - } return &UnexpectedHTTPStatusError{Status: resp.Status} }