support 'access_token' for compatibility with OAuth 2.0 https://docs.docker.com/registry/spec/auth/token/#requesting-a-token (#124)

This commit is contained in:
Jamie 2018-11-14 10:01:36 -05:00 committed by Jess Frazelle
parent 478c4dadc6
commit 83c621c4a7
2 changed files with 70 additions and 7 deletions

View file

@ -40,7 +40,18 @@ func (t *TokenTransport) RoundTrip(req *http.Request) (*http.Response, error) {
}
type authToken struct {
Token string `json:"token"`
Token string `json:"token"`
AccessToken string `json:"access_token"`
}
func (t authToken) String() (string, error) {
if t.Token != "" {
return t.Token, nil
}
if t.AccessToken != "" {
return t.AccessToken, nil
}
return "", errors.New("auth token cannot be empty")
}
func (t *TokenTransport) authAndRetry(authService *authService, req *http.Request) (*http.Response, error) {
@ -81,7 +92,8 @@ func (t *TokenTransport) auth(authService *authService) (string, *http.Response,
return "", nil, err
}
return authToken.Token, nil, nil
token, err := authToken.String()
return token, nil, err
}
func (t *TokenTransport) retry(req *http.Request, token string) (*http.Response, error) {
@ -187,11 +199,7 @@ func (r *Registry) Token(url string) (string, error) {
return "", err
}
if authToken.Token == "" {
return "", errors.New("Auth token cannot be empty")
}
return authToken.Token, nil
return authToken.String()
}
// Headers returns the authorization headers for a specific uri.

View file

@ -3,6 +3,7 @@ package registry
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/docker/docker/api/types"
@ -36,3 +37,57 @@ func TestErrBasicAuth(t *testing.T) {
t.Fatalf("expected empty token, got %v", err)
}
}
var authURI string
func oauthFlow(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/oauth2/accesstoken") {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"access_token":"abcdef1234"}`))
return
}
if strings.HasPrefix(r.URL.Path, "/oauth2/token") {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"token":"abcdef1234"}`))
return
}
auth := r.Header.Get("authorization")
if !strings.HasPrefix(auth, "Bearer") {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if authURI != "" {
w.Header().Set("www-authenticate", `Bearer realm="`+authURI+`/oauth2/token",service="my.endpoint.here"`)
}
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":null}]}`))
return
}
w.WriteHeader(http.StatusOK)
}
func TestBothTokenAndAccessTokenWork(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(oauthFlow))
defer ts.Close()
for _, which := range []string{"token", "accesstoken"} {
authURI = ts.URL + "/oauth2/" + which + "?service=my.endpoint.here"
authConfig := types.AuthConfig{
Username: "abc",
Password: "123",
ServerAddress: ts.URL,
}
authConfig.Email = "me@email.com"
r, err := New(authConfig, Opt{Insecure: true, Debug: true})
if err != nil {
t.Fatalf("expected no error creating client, got %v", err)
}
token, err := r.Token(ts.URL)
if err != nil {
t.Fatalf("err getting token from url: %v err: %v", ts.URL, err)
}
if token == "" {
t.Fatalf("error got empty token")
}
}
}