registry: unexport auth-related context utilities

The specifics of how the authorization for a request is propagated
through the registry app are private implementation details. Hide those
details from outsiders so they can be changed as needed without fear of
breaking third-party code. Move the utilities for attaching a request's
authorization status to its context and retrieving it from the context
into the registry/handlers package as unexported symbols.

Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
Cory Snider 2023-10-24 17:04:16 -04:00
parent bd80d7590d
commit 868faeec67
4 changed files with 73 additions and 75 deletions

View file

@ -32,22 +32,11 @@
package auth package auth
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
) )
const (
// UserKey is used to get the user object from
// a user context
UserKey = "auth.user"
// UserNameKey is used to get the user name from
// a user context
UserNameKey = "auth.user.name"
)
var ( var (
// ErrInvalidCredential is returned when the auth token does not authenticate correctly. // ErrInvalidCredential is returned when the auth token does not authenticate correctly.
ErrInvalidCredential = errors.New("invalid authorization credential") ErrInvalidCredential = errors.New("invalid authorization credential")
@ -115,63 +104,6 @@ type CredentialAuthenticator interface {
AuthenticateUser(username, password string) error AuthenticateUser(username, password string) error
} }
// WithUser returns a context with the authorized user info.
func WithUser(ctx context.Context, user UserInfo) context.Context {
return userInfoContext{
Context: ctx,
user: user,
}
}
type userInfoContext struct {
context.Context
user UserInfo
}
func (uic userInfoContext) Value(key interface{}) interface{} {
switch key {
case UserKey:
return uic.user
case UserNameKey:
return uic.user.Name
}
return uic.Context.Value(key)
}
// WithResources returns a context with the authorized resources.
func WithResources(ctx context.Context, resources []Resource) context.Context {
return resourceContext{
Context: ctx,
resources: resources,
}
}
type resourceContext struct {
context.Context
resources []Resource
}
type resourceKey struct{}
func (rc resourceContext) Value(key interface{}) interface{} {
if key == (resourceKey{}) {
return rc.resources
}
return rc.Context.Value(key)
}
// AuthorizedResources returns the list of resources which have
// been authorized for this request.
func AuthorizedResources(ctx context.Context) []Resource {
if resources, ok := ctx.Value(resourceKey{}).([]Resource); ok {
return resources
}
return nil
}
// InitFunc is the type of an AccessController factory function and is used // InitFunc is the type of an AccessController factory function and is used
// to register the constructor for different AccesController backends. // to register the constructor for different AccesController backends.
type InitFunc func(options map[string]interface{}) (AccessController, error) type InitFunc func(options map[string]interface{}) (AccessController, error)

View file

@ -635,7 +635,7 @@ func (app *App) dispatcher(dispatch dispatchFunc) http.Handler {
} }
// Add username to request logging // Add username to request logging
context.Context = dcontext.WithLogger(context.Context, dcontext.GetLogger(context.Context, auth.UserNameKey)) context.Context = dcontext.WithLogger(context.Context, dcontext.GetLogger(context.Context, userNameKey))
// sync up context on the request. // sync up context on the request.
r = r.WithContext(context) r = r.WithContext(context)
@ -822,10 +822,10 @@ func (app *App) authorized(w http.ResponseWriter, r *http.Request, context *Cont
return fmt.Errorf("access controller returned neither an access grant nor an error") return fmt.Errorf("access controller returned neither an access grant nor an error")
} }
ctx := auth.WithUser(context.Context, grant.User) ctx := withUser(context.Context, grant.User)
ctx = auth.WithResources(ctx, grant.Resources) ctx = withResources(ctx, grant.Resources)
dcontext.GetLogger(ctx, auth.UserNameKey).Info("authorized request") dcontext.GetLogger(ctx, userNameKey).Info("authorized request")
// TODO(stevvooe): This pattern needs to be cleaned up a bit. One context // TODO(stevvooe): This pattern needs to be cleaned up a bit. One context
// should be replaced by another, rather than replacing the context on a // should be replaced by another, rather than replacing the context on a
// mutable object. // mutable object.

View file

@ -77,10 +77,20 @@ func getUploadUUID(ctx context.Context) (uuid string) {
return dcontext.GetStringValue(ctx, "vars.uuid") return dcontext.GetStringValue(ctx, "vars.uuid")
} }
const (
// userKey is used to get the user object from
// a user context
userKey = "auth.user"
// userNameKey is used to get the user name from
// a user context
userNameKey = "auth.user.name"
)
// getUserName attempts to resolve a username from the context and request. If // getUserName attempts to resolve a username from the context and request. If
// a username cannot be resolved, the empty string is returned. // a username cannot be resolved, the empty string is returned.
func getUserName(ctx context.Context, r *http.Request) string { func getUserName(ctx context.Context, r *http.Request) string {
username := dcontext.GetStringValue(ctx, auth.UserNameKey) username := dcontext.GetStringValue(ctx, userNameKey)
// Fallback to request user with basic auth // Fallback to request user with basic auth
if username == "" { if username == "" {
@ -93,3 +103,60 @@ func getUserName(ctx context.Context, r *http.Request) string {
return username return username
} }
// withUser returns a context with the authorized user info.
func withUser(ctx context.Context, user auth.UserInfo) context.Context {
return userInfoContext{
Context: ctx,
user: user,
}
}
type userInfoContext struct {
context.Context
user auth.UserInfo
}
func (uic userInfoContext) Value(key interface{}) interface{} {
switch key {
case userKey:
return uic.user
case userNameKey:
return uic.user.Name
}
return uic.Context.Value(key)
}
// withResources returns a context with the authorized resources.
func withResources(ctx context.Context, resources []auth.Resource) context.Context {
return resourceContext{
Context: ctx,
resources: resources,
}
}
type resourceContext struct {
context.Context
resources []auth.Resource
}
type resourceKey struct{}
func (rc resourceContext) Value(key interface{}) interface{} {
if key == (resourceKey{}) {
return rc.resources
}
return rc.Context.Value(key)
}
// authorizedResources returns the list of resources which have
// been authorized for this request.
func authorizedResources(ctx context.Context) []auth.Resource {
if resources, ok := ctx.Value(resourceKey{}).([]auth.Resource); ok {
return resources
}
return nil
}

View file

@ -13,7 +13,6 @@ import (
"github.com/distribution/distribution/v3/manifest/ocischema" "github.com/distribution/distribution/v3/manifest/ocischema"
"github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/manifest/schema2"
"github.com/distribution/distribution/v3/registry/api/errcode" "github.com/distribution/distribution/v3/registry/api/errcode"
"github.com/distribution/distribution/v3/registry/auth"
"github.com/distribution/distribution/v3/registry/storage/driver" "github.com/distribution/distribution/v3/registry/storage/driver"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
@ -394,7 +393,7 @@ func (imh *manifestHandler) applyResourcePolicy(manifest distribution.Manifest)
return errcode.ErrorCodeDenied.WithMessage(fmt.Sprintf("registry does not allow %s manifest", class)) return errcode.ErrorCodeDenied.WithMessage(fmt.Sprintf("registry does not allow %s manifest", class))
} }
resources := auth.AuthorizedResources(imh) resources := authorizedResources(imh)
n := imh.Repository.Named().Name() n := imh.Repository.Named().Name()
var foundResource bool var foundResource bool