Merge pull request #2711 from davidswu/autoredirect

add autoredirect auth config
This commit is contained in:
Derek McGowan 2018-11-27 15:48:25 -08:00 committed by GitHub
commit aa985ba889
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 48 additions and 29 deletions

View file

@ -245,7 +245,7 @@ func (ts *tokenServer) getToken(ctx context.Context, w http.ResponseWriter, r *h
// Get response context. // Get response context.
ctx, w = dcontext.WithResponseWriter(ctx, w) ctx, w = dcontext.WithResponseWriter(ctx, w)
challenge.SetHeaders(w) challenge.SetHeaders(r, w)
handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail(challenge.Error()), w) handleError(ctx, errcode.ErrorCodeUnauthorized.WithDetail(challenge.Error()), w)
dcontext.GetResponseLogger(ctx).Info("get token authentication challenge") dcontext.GetResponseLogger(ctx).Info("get token authentication challenge")

View file

@ -21,7 +21,7 @@
// if ctx, err := accessController.Authorized(ctx, access); err != nil { // if ctx, err := accessController.Authorized(ctx, access); err != nil {
// if challenge, ok := err.(auth.Challenge) { // if challenge, ok := err.(auth.Challenge) {
// // Let the challenge write the response. // // Let the challenge write the response.
// challenge.SetHeaders(w) // challenge.SetHeaders(r, w)
// w.WriteHeader(http.StatusUnauthorized) // w.WriteHeader(http.StatusUnauthorized)
// return // return
// } else { // } else {
@ -87,7 +87,7 @@ type Challenge interface {
// adding the an HTTP challenge header on the response message. Callers // adding the an HTTP challenge header on the response message. Callers
// are expected to set the appropriate HTTP status code (e.g. 401) // are expected to set the appropriate HTTP status code (e.g. 401)
// themselves. // themselves.
SetHeaders(w http.ResponseWriter) SetHeaders(r *http.Request, 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

@ -111,7 +111,7 @@ type challenge struct {
var _ auth.Challenge = challenge{} var _ auth.Challenge = challenge{}
// SetHeaders sets the basic challenge header on the response. // SetHeaders sets the basic challenge header on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) { func (ch challenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm)) w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", ch.realm))
} }

View file

@ -50,7 +50,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.SetHeaders(w) err.SetHeaders(r, w)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return return
default: default:

View file

@ -82,7 +82,7 @@ type challenge struct {
var _ auth.Challenge = challenge{} var _ auth.Challenge = challenge{}
// SetHeaders sets a simple bearer challenge on the response. // SetHeaders sets a simple bearer challenge on the response.
func (ch challenge) SetHeaders(w http.ResponseWriter) { func (ch challenge) SetHeaders(r *http.Request, 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 != "" {

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.SetHeaders(w) err.SetHeaders(r, w)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return return
default: default:

View file

@ -76,10 +76,11 @@ var (
// authChallenge implements the auth.Challenge interface. // authChallenge implements the auth.Challenge interface.
type authChallenge struct { type authChallenge struct {
err error err error
realm string realm string
service string autoRedirect bool
accessSet accessSet service string
accessSet accessSet
} }
var _ auth.Challenge = authChallenge{} var _ auth.Challenge = authChallenge{}
@ -97,8 +98,14 @@ func (ac authChallenge) Status() int {
// 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(r *http.Request) string {
str := fmt.Sprintf("Bearer realm=%q,service=%q", ac.realm, ac.service) var realm string
if ac.autoRedirect {
realm = fmt.Sprintf("https://%s/auth/token", r.Host)
} else {
realm = ac.realm
}
str := fmt.Sprintf("Bearer realm=%q,service=%q", realm, ac.service)
if scope := ac.accessSet.scopeParam(); scope != "" { if scope := ac.accessSet.scopeParam(); scope != "" {
str = fmt.Sprintf("%s,scope=%q", str, scope) str = fmt.Sprintf("%s,scope=%q", str, scope)
@ -114,23 +121,25 @@ func (ac authChallenge) challengeParams() string {
} }
// SetChallenge sets the WWW-Authenticate value for the response. // SetChallenge sets the WWW-Authenticate value for the response.
func (ac authChallenge) SetHeaders(w http.ResponseWriter) { func (ac authChallenge) SetHeaders(r *http.Request, w http.ResponseWriter) {
w.Header().Add("WWW-Authenticate", ac.challengeParams()) w.Header().Add("WWW-Authenticate", ac.challengeParams(r))
} }
// accessController implements the auth.AccessController interface. // accessController implements the auth.AccessController interface.
type accessController struct { type accessController struct {
realm string realm string
issuer string autoRedirect bool
service string issuer string
rootCerts *x509.CertPool service string
trustedKeys map[string]libtrust.PublicKey rootCerts *x509.CertPool
trustedKeys map[string]libtrust.PublicKey
} }
// tokenAccessOptions is a convenience type for handling // tokenAccessOptions is a convenience type for handling
// options to the contstructor of an accessController. // options to the contstructor of an accessController.
type tokenAccessOptions struct { type tokenAccessOptions struct {
realm string realm string
autoRedirect bool
issuer string issuer string
service string service string
rootCertBundle string rootCertBundle string
@ -153,6 +162,12 @@ func checkOptions(options map[string]interface{}) (tokenAccessOptions, error) {
opts.realm, opts.issuer, opts.service, opts.rootCertBundle = vals[0], vals[1], vals[2], vals[3] opts.realm, opts.issuer, opts.service, opts.rootCertBundle = vals[0], vals[1], vals[2], vals[3]
autoRedirect, ok := options["autoredirect"].(bool)
if !ok {
return opts, fmt.Errorf("token auth requires a valid option bool: autoredirect")
}
opts.autoRedirect = autoRedirect
return opts, nil return opts, nil
} }
@ -205,11 +220,12 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
} }
return &accessController{ return &accessController{
realm: config.realm, realm: config.realm,
issuer: config.issuer, autoRedirect: config.autoRedirect,
service: config.service, issuer: config.issuer,
rootCerts: rootPool, service: config.service,
trustedKeys: trustedKeys, rootCerts: rootPool,
trustedKeys: trustedKeys,
}, nil }, nil
} }
@ -217,9 +233,10 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
// for actions on resources described by the given access items. // for actions on resources described by the given access items.
func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.Access) (context.Context, error) { func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.Access) (context.Context, error) {
challenge := &authChallenge{ challenge := &authChallenge{
realm: ac.realm, realm: ac.realm,
service: ac.service, autoRedirect: ac.autoRedirect,
accessSet: newAccessSet(accessItems...), service: ac.service,
accessSet: newAccessSet(accessItems...),
} }
req, err := dcontext.GetRequest(ctx) req, err := dcontext.GetRequest(ctx)

View file

@ -333,6 +333,7 @@ func TestAccessController(t *testing.T) {
"issuer": issuer, "issuer": issuer,
"service": service, "service": service,
"rootcertbundle": rootCertBundleFilename, "rootcertbundle": rootCertBundleFilename,
"autoredirect": false,
} }
accessController, err := newAccessController(options) accessController, err := newAccessController(options)
@ -518,6 +519,7 @@ func TestNewAccessControllerPemBlock(t *testing.T) {
"issuer": issuer, "issuer": issuer,
"service": service, "service": service,
"rootcertbundle": rootCertBundleFilename, "rootcertbundle": rootCertBundleFilename,
"autoredirect": false,
} }
ac, err := newAccessController(options) ac, err := newAccessController(options)

View file

@ -847,7 +847,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.SetHeaders(w) err.SetHeaders(r, w)
if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil { if err := errcode.ServeJSON(w, errcode.ErrorCodeUnauthorized.WithDetail(accessRecords)); err != nil {
dcontext.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors) dcontext.GetLogger(context).Errorf("error serving error json: %v (from %v)", err, context.Errors)