Use context for auth access controllers

The auth package has been updated to use "golang.org/x/net/context" for
passing information between the application and the auth backend.

AccessControllers should now set a "auth.user" context value to a AuthUser
struct containing a single "Name" field for now with possible, optional, values
in the future.

The "silly" auth backend always sets the name to "silly", while the "token" auth
backend will set the name to match the "subject" claim of the JWT.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
This commit is contained in:
Josh Hawn 2015-02-03 17:59:24 -08:00
parent c1c7d3dabf
commit 2c3d738a05
12 changed files with 1134 additions and 31 deletions

View file

@ -11,9 +11,9 @@ import (
"os"
"strings"
"github.com/docker/libtrust"
"github.com/docker/distribution/auth"
"github.com/docker/libtrust"
"golang.org/x/net/context"
)
// accessSet maps a typed, named resource to
@ -217,18 +217,23 @@ func newAccessController(options map[string]interface{}) (auth.AccessController,
// Authorized handles checking whether the given request is authorized
// for actions on resources described by the given access items.
func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Access) error {
func (ac *accessController) Authorized(ctx context.Context, accessItems ...auth.Access) (context.Context, error) {
challenge := &authChallenge{
realm: ac.realm,
service: ac.service,
accessSet: newAccessSet(accessItems...),
}
req, err := auth.RequestFromContext(ctx)
if err != nil {
return nil, err
}
parts := strings.Split(req.Header.Get("Authorization"), " ")
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
challenge.err = ErrTokenRequired
return challenge
return nil, challenge
}
rawToken := parts[1]
@ -236,7 +241,7 @@ func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Ac
token, err := NewToken(rawToken)
if err != nil {
challenge.err = err
return challenge
return nil, challenge
}
verifyOpts := VerifyOptions{
@ -248,18 +253,18 @@ func (ac *accessController) Authorized(req *http.Request, accessItems ...auth.Ac
if err = token.Verify(verifyOpts); err != nil {
challenge.err = err
return challenge
return nil, challenge
}
accessSet := token.accessSet()
for _, access := range accessItems {
if !accessSet.contains(access) {
challenge.err = ErrInsufficientScope
return challenge
return nil, challenge
}
}
return nil
return context.WithValue(ctx, "auth.user", auth.UserInfo{Name: token.Claims.Subject}), nil
}
// init handles registering the token auth backend.

View file

@ -47,7 +47,7 @@ type ClaimSet struct {
JWTID string `json:"jti"`
// Private claims
Access []*ResourceActions
Access []*ResourceActions `json:"access"`
}
// Header describes the header section of a JSON Web Token.

View file

@ -17,6 +17,7 @@ import (
"github.com/docker/distribution/auth"
"github.com/docker/libtrust"
"golang.org/x/net/context"
)
func makeRootKeys(numKeys int) ([]libtrust.PrivateKey, error) {
@ -282,7 +283,8 @@ func TestAccessController(t *testing.T) {
Action: "baz",
}
err = accessController.Authorized(req, testAccess)
ctx := context.WithValue(nil, "http.request", req)
authCtx, err := accessController.Authorized(ctx, testAccess)
challenge, ok := err.(auth.Challenge)
if !ok {
t.Fatal("accessController did not return a challenge")
@ -292,6 +294,10 @@ func TestAccessController(t *testing.T) {
t.Fatalf("accessControler did not get expected error - got %s - expected %s", challenge, ErrTokenRequired)
}
if authCtx != nil {
t.Fatalf("expected nil auth context but got %s", authCtx)
}
// 2. Supply an invalid token.
token, err := makeTestToken(
issuer, service,
@ -308,7 +314,7 @@ func TestAccessController(t *testing.T) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
err = accessController.Authorized(req, testAccess)
authCtx, err = accessController.Authorized(ctx, testAccess)
challenge, ok = err.(auth.Challenge)
if !ok {
t.Fatal("accessController did not return a challenge")
@ -318,6 +324,10 @@ func TestAccessController(t *testing.T) {
t.Fatalf("accessControler did not get expected error - got %s - expected %s", challenge, ErrTokenRequired)
}
if authCtx != nil {
t.Fatalf("expected nil auth context but got %s", authCtx)
}
// 3. Supply a token with insufficient access.
token, err = makeTestToken(
issuer, service,
@ -330,7 +340,7 @@ func TestAccessController(t *testing.T) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
err = accessController.Authorized(req, testAccess)
authCtx, err = accessController.Authorized(ctx, testAccess)
challenge, ok = err.(auth.Challenge)
if !ok {
t.Fatal("accessController did not return a challenge")
@ -340,6 +350,10 @@ func TestAccessController(t *testing.T) {
t.Fatalf("accessControler did not get expected error - got %s - expected %s", challenge, ErrInsufficientScope)
}
if authCtx != nil {
t.Fatalf("expected nil auth context but got %s", authCtx)
}
// 4. Supply the token we need, or deserve, or whatever.
token, err = makeTestToken(
issuer, service,
@ -348,7 +362,7 @@ func TestAccessController(t *testing.T) {
Name: testAccess.Name,
Actions: []string{testAccess.Action},
}},
rootKeys[0], 1, // Everything is valid except the key which signed it.
rootKeys[0], 1,
)
if err != nil {
t.Fatal(err)
@ -356,7 +370,17 @@ func TestAccessController(t *testing.T) {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.compactRaw()))
if err = accessController.Authorized(req, testAccess); err != nil {
authCtx, err = accessController.Authorized(ctx, testAccess)
if err != nil {
t.Fatalf("accessController returned unexpected error: %s", err)
}
userInfo, ok := authCtx.Value("auth.user").(auth.UserInfo)
if !ok {
t.Fatal("token accessController did not set auth.user context")
}
if userInfo.Name != "foo" {
t.Fatalf("expected user name %q, got %q", "foo", userInfo.Name)
}
}