Merge pull request #736 from stevvooe/authorization-interface-cleanup

Authorization interface cleanup
This commit is contained in:
Stephen Day 2015-07-24 15:39:49 -07:00
commit a6ef6c0dc3
7 changed files with 36 additions and 38 deletions

View file

@ -34,7 +34,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"golang.org/x/net/context" "github.com/docker/distribution/context"
) )
// UserInfo carries information about // UserInfo carries information about
@ -61,12 +61,12 @@ type Access struct {
// header values based on the error. // header values based on the error.
type Challenge interface { type Challenge interface {
error error
// ServeHTTP prepares the request to conduct the appropriate challenge
// response by adding the appropriate HTTP challenge header on the response // SetHeaders prepares the request to conduct a challenge response by
// message. Callers are expected to set the appropriate HTTP status code // adding the an HTTP challenge header on the response message. Callers
// (e.g. 401) themselves. Because no body is written, users may write a // are expected to set the appropriate HTTP status code (e.g. 401)
// custom body after calling ServeHTTP. // themselves.
ServeHTTP(w http.ResponseWriter, r *http.Request) SetHeaders(w http.ResponseWriter)
} }
// AccessController controls access to registry resources based on a request // AccessController controls access to registry resources based on a request

View file

@ -11,9 +11,8 @@ import (
"net/http" "net/http"
"os" "os"
ctxu "github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/docker/distribution/registry/auth" "github.com/docker/distribution/registry/auth"
"golang.org/x/net/context"
) )
var ( var (
@ -57,7 +56,7 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
} }
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) { func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
req, err := ctxu.GetRequest(ctx) req, err := context.GetRequest(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -71,7 +70,7 @@ func (ac *accessController) Authorized(ctx context.Context, accessRecords ...aut
} }
if err := ac.htpasswd.authenticateUser(username, password); err != nil { if err := ac.htpasswd.authenticateUser(username, password); err != nil {
ctxu.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err) context.GetLogger(ctx).Errorf("error authenticating user %q: %v", username, err)
return nil, &challenge{ return nil, &challenge{
realm: ac.realm, realm: ac.realm,
err: ErrAuthenticationFailure, err: ErrAuthenticationFailure,
@ -87,12 +86,14 @@ type challenge struct {
err error err error
} }
func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) { var _ auth.Challenge = challenge{}
header := fmt.Sprintf("Basic realm=%q", ch.realm)
w.Header().Set("WWW-Authenticate", header) // SetHeaders sets the basic challenge header on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm))
} }
func (ch *challenge) Error() string { func (ch challenge) Error() string {
return fmt.Sprintf("basic authentication challenge: %#v", ch) return fmt.Sprintf("basic authentication challenge: %#v", ch)
} }

View file

@ -48,7 +48,7 @@ func TestBasicAccessController(t *testing.T) {
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {
case auth.Challenge: case auth.Challenge:
err.ServeHTTP(w, r) err.SetHeaders(w)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return return
default: default:

View file

@ -12,9 +12,8 @@ import (
"net/http" "net/http"
"strings" "strings"
ctxu "github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/docker/distribution/registry/auth" "github.com/docker/distribution/registry/auth"
"golang.org/x/net/context"
) )
// accessController provides a simple implementation of auth.AccessController // accessController provides a simple implementation of auth.AccessController
@ -44,7 +43,7 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
// Authorized simply checks for the existence of the authorization header, // Authorized simply checks for the existence of the authorization header,
// responding with a bearer challenge if it doesn't exist. // responding with a bearer challenge if it doesn't exist.
func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) { func (ac *accessController) Authorized(ctx context.Context, accessRecords ...auth.Access) (context.Context, error) {
req, err := ctxu.GetRequest(ctx) req, err := context.GetRequest(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -75,7 +74,10 @@ type challenge struct {
scope string scope string
} }
func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) { var _ auth.Challenge = challenge{}
// SetHeaders sets a simple bearer challenge on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) {
header := fmt.Sprintf("Bearer realm=%q,service=%q", ch.realm, ch.service) header := fmt.Sprintf("Bearer realm=%q,service=%q", ch.realm, ch.service)
if ch.scope != "" { if ch.scope != "" {
@ -85,7 +87,7 @@ func (ch *challenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("WWW-Authenticate", header) w.Header().Set("WWW-Authenticate", header)
} }
func (ch *challenge) Error() string { func (ch challenge) Error() string {
return fmt.Sprintf("silly authentication challenge: %#v", ch) return fmt.Sprintf("silly authentication challenge: %#v", ch)
} }

View file

@ -21,7 +21,7 @@ func TestSillyAccessController(t *testing.T) {
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {
case auth.Challenge: case auth.Challenge:
err.ServeHTTP(w, r) err.SetHeaders(w)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return return
default: default:

View file

@ -11,10 +11,9 @@ import (
"os" "os"
"strings" "strings"
ctxu "github.com/docker/distribution/context" "github.com/docker/distribution/context"
"github.com/docker/distribution/registry/auth" "github.com/docker/distribution/registry/auth"
"github.com/docker/libtrust" "github.com/docker/libtrust"
"golang.org/x/net/context"
) )
// accessSet maps a typed, named resource to // accessSet maps a typed, named resource to
@ -82,20 +81,22 @@ type authChallenge struct {
accessSet accessSet accessSet accessSet
} }
var _ auth.Challenge = authChallenge{}
// Error returns the internal error string for this authChallenge. // Error returns the internal error string for this authChallenge.
func (ac *authChallenge) Error() string { func (ac authChallenge) Error() string {
return ac.err.Error() return ac.err.Error()
} }
// Status returns the HTTP Response Status Code for this authChallenge. // Status returns the HTTP Response Status Code for this authChallenge.
func (ac *authChallenge) Status() int { func (ac authChallenge) Status() int {
return http.StatusUnauthorized return http.StatusUnauthorized
} }
// challengeParams constructs the value to be used in // challengeParams constructs the value to be used in
// the WWW-Authenticate response challenge header. // the WWW-Authenticate response challenge header.
// See https://tools.ietf.org/html/rfc6750#section-3 // See https://tools.ietf.org/html/rfc6750#section-3
func (ac *authChallenge) challengeParams() string { func (ac authChallenge) challengeParams() string {
str := fmt.Sprintf("Bearer realm=%q,service=%q", ac.realm, ac.service) str := fmt.Sprintf("Bearer realm=%q,service=%q", ac.realm, ac.service)
if scope := ac.accessSet.scopeParam(); scope != "" { if scope := ac.accessSet.scopeParam(); scope != "" {
@ -111,15 +112,9 @@ func (ac *authChallenge) challengeParams() string {
return str return str
} }
// SetHeader sets the WWW-Authenticate value for the given header. // SetChallenge sets the WWW-Authenticate value for the response.
func (ac *authChallenge) SetHeader(header http.Header) { func (ac authChallenge) SetHeaders(w http.ResponseWriter) {
header.Add("WWW-Authenticate", ac.challengeParams()) w.Header().Add("WWW-Authenticate", ac.challengeParams())
}
// ServeHttp handles writing the challenge response
// by setting the challenge header.
func (ac *authChallenge) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ac.SetHeader(w.Header())
} }
// accessController implements the auth.AccessController interface. // accessController implements the auth.AccessController interface.
@ -224,7 +219,7 @@ func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.
accessSet: newAccessSet(accessItems...), accessSet: newAccessSet(accessItems...),
} }
req, err := ctxu.GetRequest(ctx) req, err := context.GetRequest(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -518,7 +518,7 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
switch err := err.(type) { switch err := err.(type) {
case auth.Challenge: case auth.Challenge:
// Add the appropriate WWW-Auth header // Add the appropriate WWW-Auth header
err.ServeHTTP(w, r) err.SetHeaders(w)
if err := errcode.ServeJSON(w, v2.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil { if err := errcode.ServeJSON(w, v2.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors) ctxu.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)