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:
Derek McGowan 2016-11-09 14:57:53 -08:00
parent a1a73884f9
commit 16396a7a80
No known key found for this signature in database
GPG key ID: F58C5D0A4405ACDB

View file

@ -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}
} }