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:
parent
c1c7d3dabf
commit
2c3d738a05
12 changed files with 1134 additions and 31 deletions
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue