diff --git a/internal/client/errors.go b/internal/client/errors.go index 5131ef813..51d80178b 100644 --- a/internal/client/errors.go +++ b/internal/client/errors.go @@ -44,7 +44,6 @@ func parseHTTPErrorResponse(resp *http.Response) error { if err != nil { return err } - statusCode := resp.StatusCode // A HEAD request for example validly does not contain any body, while @@ -53,6 +52,10 @@ func parseHTTPErrorResponse(resp *http.Response) error { return makeError(statusCode, "") } + if statusCode == 401 { + return makeError(statusCode, string(body)) + } + ctHeader := resp.Header.Get("Content-Type") if ctHeader == "" { return makeError(statusCode, string(body)) @@ -101,13 +104,13 @@ func parseHTTPErrorResponse(resp *http.Response) error { func makeError(statusCode int, details string) error { switch statusCode { case http.StatusUnauthorized: - return errcode.ErrorCodeUnauthorized.WithMessage(details) + return errcode.ErrorCodeUnauthorized.WithMessageStatusCode(details, statusCode) case http.StatusForbidden: - return errcode.ErrorCodeDenied.WithMessage(details) + return errcode.ErrorCodeDenied.WithMessageStatusCode(details, statusCode) case http.StatusTooManyRequests: - return errcode.ErrorCodeTooManyRequests.WithMessage(details) + return errcode.ErrorCodeTooManyRequests.WithMessageStatusCode(details, statusCode) default: - return errcode.ErrorCodeUnknown.WithMessage(details) + return errcode.ErrorCodeUnknown.WithMessageStatusCode(details, statusCode) } } @@ -138,6 +141,7 @@ func HandleHTTPResponseError(resp *http.Response) error { for _, c := range challenge.ResponseChallenges(resp) { if c.Scheme == "bearer" { var err errcode.Error + err.StatusCode = resp.StatusCode // codes defined at https://tools.ietf.org/html/rfc6750#section-3.1 switch c.Parameters["error"] { case "invalid_token": diff --git a/registry/api/errcode/errors.go b/registry/api/errcode/errors.go index a8b52c188..6f0d49545 100644 --- a/registry/api/errcode/errors.go +++ b/registry/api/errcode/errors.go @@ -78,6 +78,16 @@ func (ec ErrorCode) WithMessage(message string) Error { } } +// WithMessage creates a new Error struct based on the passed-in info and +// overrides the Message property. +func (ec ErrorCode) WithMessageStatusCode(message string, statusCode int) Error { + return Error{ + Code: ec, + Message: message, + StatusCode: statusCode, + } +} + // WithDetail creates a new Error struct based on the passed-in info and // set the Detail property appropriately func (ec ErrorCode) WithDetail(detail interface{}) Error { @@ -97,10 +107,10 @@ func (ec ErrorCode) WithArgs(args ...interface{}) Error { // Error provides a wrapper around ErrorCode with extra Details provided. type Error struct { - Code ErrorCode `json:"code"` - Message string `json:"message"` - Detail interface{} `json:"detail,omitempty"` - + Code ErrorCode `json:"code"` + Message string `json:"message"` + Detail interface{} `json:"detail,omitempty"` + StatusCode int `json:"statusCode"` // TODO(duglin): See if we need an "args" property so we can do the // variable substitution right before showing the message to the user } @@ -121,9 +131,10 @@ func (e Error) Error() string { // some Detail info added func (e Error) WithDetail(detail interface{}) Error { return Error{ - Code: e.Code, - Message: e.Message, - Detail: detail, + Code: e.Code, + StatusCode: e.StatusCode, + Message: e.Message, + Detail: detail, } } diff --git a/registry/handlers/manifests.go b/registry/handlers/manifests.go index 6b7648c2b..bfe7a53b6 100644 --- a/registry/handlers/manifests.go +++ b/registry/handlers/manifests.go @@ -2,6 +2,7 @@ package handlers import ( "bytes" + "errors" "fmt" "mime" "net/http" @@ -142,6 +143,8 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) if err != nil { if _, ok := err.(distribution.ErrManifestUnknownRevision); ok { imh.Errors = append(imh.Errors, errcode.ErrorCodeManifestUnknown.WithDetail(err)) + } else if checkAllErrorsStatusCode(err, 401) { + imh.Errors = append(imh.Errors, errcode.ErrorCodeUnauthorized.WithDetail(err)) } else { imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) } @@ -225,6 +228,21 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) } } +func checkAllErrorsStatusCode(err error, statusCode int) bool { + var errs errcode.Errors + if !errors.As(err, &errs) { + return false + } + for _, e := range errs { + var specificErr errcode.Error + ok := errors.As(e, &specificErr) + if !ok || specificErr.StatusCode != statusCode { + return false + } + } + return true +} + func etagMatch(r *http.Request, etag string) bool { for _, headerVal := range r.Header["If-None-Match"] { if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted