Fetch token by credentials and refresh token
Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
becdd83131
commit
f49bf18768
3 changed files with 348 additions and 74 deletions
|
@ -36,6 +36,14 @@ type AuthenticationHandler interface {
|
||||||
type CredentialStore interface {
|
type CredentialStore interface {
|
||||||
// Basic returns basic auth for the given URL
|
// Basic returns basic auth for the given URL
|
||||||
Basic(*url.URL) (string, string)
|
Basic(*url.URL) (string, string)
|
||||||
|
|
||||||
|
// RefreshToken returns a refresh token for the
|
||||||
|
// given URL and service
|
||||||
|
RefreshToken(*url.URL, string) string
|
||||||
|
|
||||||
|
// SetRefreshToken sets the refresh token if none
|
||||||
|
// is provided for the given url and service
|
||||||
|
SetRefreshToken(realm *url.URL, service, token string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthorizer creates an authorizer which can handle multiple authentication
|
// NewAuthorizer creates an authorizer which can handle multiple authentication
|
||||||
|
@ -196,95 +204,73 @@ func (th *tokenHandler) refreshToken(params map[string]string, additionalScopes
|
||||||
}
|
}
|
||||||
now := th.clock.Now()
|
now := th.clock.Now()
|
||||||
if now.After(th.tokenExpiration) || addedScopes {
|
if now.After(th.tokenExpiration) || addedScopes {
|
||||||
tr, err := th.fetchToken(params)
|
token, expiration, err := th.fetchToken(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
th.tokenCache = tr.Token
|
|
||||||
th.tokenExpiration = tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second)
|
// do not update cache for added scope tokens
|
||||||
|
if !addedScopes {
|
||||||
|
th.tokenCache = token
|
||||||
|
th.tokenExpiration = expiration
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type tokenResponse struct {
|
type postTokenResponse struct {
|
||||||
Token string `json:"token"`
|
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
ExpiresIn int `json:"expires_in"`
|
ExpiresIn int `json:"expires_in"`
|
||||||
IssuedAt time.Time `json:"issued_at"`
|
IssuedAt time.Time `json:"issued_at"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenResponse, err error) {
|
func (th *tokenHandler) fetchTokenWithOAuth(realm *url.URL, refreshToken, service string, scopes []string) (token string, expiration time.Time, err error) {
|
||||||
realm, ok := params["realm"]
|
form := url.Values{}
|
||||||
if !ok {
|
form.Set("scope", strings.Join(scopes, " "))
|
||||||
return nil, errors.New("no realm specified for token auth challenge")
|
form.Set("service", service)
|
||||||
|
|
||||||
|
// TODO: Make this configurable
|
||||||
|
form.Set("client_id", "docker")
|
||||||
|
|
||||||
|
if refreshToken != "" {
|
||||||
|
form.Set("grant_type", "refresh_token")
|
||||||
|
form.Set("refresh_token", refreshToken)
|
||||||
|
} else if th.creds != nil {
|
||||||
|
form.Set("grant_type", "password")
|
||||||
|
username, password := th.creds.Basic(realm)
|
||||||
|
form.Set("username", username)
|
||||||
|
form.Set("password", password)
|
||||||
|
|
||||||
|
// attempt to get a refresh token
|
||||||
|
form.Set("access_type", "offline")
|
||||||
|
} else {
|
||||||
|
// refuse to do oauth without a grant type
|
||||||
|
return "", time.Time{}, fmt.Errorf("no supported grant type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(dmcgowan): Handle empty scheme
|
resp, err := th.client().PostForm(realm.String(), form)
|
||||||
|
|
||||||
realmURL, err := url.Parse(realm)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid token auth challenge realm: %s", err)
|
return "", time.Time{}, err
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("GET", realmURL.String(), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqParams := req.URL.Query()
|
|
||||||
service := params["service"]
|
|
||||||
scope := th.scope.String()
|
|
||||||
|
|
||||||
if service != "" {
|
|
||||||
reqParams.Add("service", service)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, scopeField := range strings.Fields(scope) {
|
|
||||||
reqParams.Add("scope", scopeField)
|
|
||||||
}
|
|
||||||
|
|
||||||
for scope := range th.additionalScopes {
|
|
||||||
reqParams.Add("scope", scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
if th.creds != nil {
|
|
||||||
username, password := th.creds.Basic(realmURL)
|
|
||||||
if username != "" && password != "" {
|
|
||||||
reqParams.Add("account", username)
|
|
||||||
req.SetBasicAuth(username, password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req.URL.RawQuery = reqParams.Encode()
|
|
||||||
|
|
||||||
resp, err := th.client().Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if !client.SuccessStatus(resp.StatusCode) {
|
if !client.SuccessStatus(resp.StatusCode) {
|
||||||
err := client.HandleErrorResponse(resp)
|
err := client.HandleErrorResponse(resp)
|
||||||
return nil, err
|
return "", time.Time{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
decoder := json.NewDecoder(resp.Body)
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
tr := new(tokenResponse)
|
var tr postTokenResponse
|
||||||
if err = decoder.Decode(tr); err != nil {
|
if err = decoder.Decode(&tr); err != nil {
|
||||||
return nil, fmt.Errorf("unable to decode token response: %s", err)
|
return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `access_token` is equivalent to `token` and if both are specified
|
if tr.RefreshToken != "" && tr.RefreshToken != refreshToken {
|
||||||
// the choice is undefined. Canonicalize `access_token` by sticking
|
th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
|
||||||
// things in `token`.
|
|
||||||
if tr.AccessToken != "" {
|
|
||||||
tr.Token = tr.AccessToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if tr.Token == "" {
|
|
||||||
return nil, errors.New("authorization server did not include a token in the response")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||||
|
@ -295,10 +281,128 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenRespon
|
||||||
|
|
||||||
if tr.IssuedAt.IsZero() {
|
if tr.IssuedAt.IsZero() {
|
||||||
// issued_at is optional in the token response.
|
// issued_at is optional in the token response.
|
||||||
tr.IssuedAt = th.clock.Now()
|
tr.IssuedAt = th.clock.Now().UTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
return tr, nil
|
return tr.AccessToken, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type getTokenResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
IssuedAt time.Time `json:"issued_at"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string, scopes []string) (token string, expiration time.Time, err error) {
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", realm.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqParams := req.URL.Query()
|
||||||
|
|
||||||
|
if service != "" {
|
||||||
|
reqParams.Add("service", service)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scope := range scopes {
|
||||||
|
reqParams.Add("scope", scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
if th.creds != nil {
|
||||||
|
username, password := th.creds.Basic(realm)
|
||||||
|
if username != "" && password != "" {
|
||||||
|
reqParams.Add("account", username)
|
||||||
|
req.SetBasicAuth(username, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.URL.RawQuery = reqParams.Encode()
|
||||||
|
|
||||||
|
resp, err := th.client().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if !client.SuccessStatus(resp.StatusCode) {
|
||||||
|
err := client.HandleErrorResponse(resp)
|
||||||
|
return "", time.Time{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
|
||||||
|
var tr getTokenResponse
|
||||||
|
if err = decoder.Decode(&tr); err != nil {
|
||||||
|
return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tr.RefreshToken != "" && th.creds != nil {
|
||||||
|
th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// `access_token` is equivalent to `token` and if both are specified
|
||||||
|
// the choice is undefined. Canonicalize `access_token` by sticking
|
||||||
|
// things in `token`.
|
||||||
|
if tr.AccessToken != "" {
|
||||||
|
tr.Token = tr.AccessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if tr.Token == "" {
|
||||||
|
return "", time.Time{}, errors.New("authorization server did not include a token in the response")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
||||||
|
// The default/minimum lifetime.
|
||||||
|
tr.ExpiresIn = minimumTokenLifetimeSeconds
|
||||||
|
logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tr.IssuedAt.IsZero() {
|
||||||
|
// issued_at is optional in the token response.
|
||||||
|
tr.IssuedAt = th.clock.Now().UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr.Token, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (th *tokenHandler) fetchToken(params map[string]string) (token string, expiration time.Time, err error) {
|
||||||
|
realm, ok := params["realm"]
|
||||||
|
if !ok {
|
||||||
|
return "", time.Time{}, errors.New("no realm specified for token auth challenge")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dmcgowan): Handle empty scheme and relative realm
|
||||||
|
realmURL, err := url.Parse(realm)
|
||||||
|
if err != nil {
|
||||||
|
return "", time.Time{}, fmt.Errorf("invalid token auth challenge realm: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service := params["service"]
|
||||||
|
|
||||||
|
scopes := make([]string, 0, 1+len(th.additionalScopes))
|
||||||
|
if len(th.scope.Actions) > 0 {
|
||||||
|
scopes = append(scopes, th.scope.String())
|
||||||
|
}
|
||||||
|
for scope := range th.additionalScopes {
|
||||||
|
scopes = append(scopes, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
var refreshToken string
|
||||||
|
|
||||||
|
if th.creds != nil {
|
||||||
|
refreshToken = th.creds.RefreshToken(realmURL, service)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dmcgowan): define parameter to force oauth with password
|
||||||
|
if refreshToken != "" {
|
||||||
|
return th.fetchTokenWithOAuth(realmURL, refreshToken, service, scopes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return th.fetchTokenWithBasicAuth(realmURL, service, scopes)
|
||||||
}
|
}
|
||||||
|
|
||||||
type basicHandler struct {
|
type basicHandler struct {
|
||||||
|
|
|
@ -82,12 +82,23 @@ func ping(manager ChallengeManager, endpoint, versionHeader string) ([]APIVersio
|
||||||
type testCredentialStore struct {
|
type testCredentialStore struct {
|
||||||
username string
|
username string
|
||||||
password string
|
password string
|
||||||
|
refreshTokens map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tcs *testCredentialStore) Basic(*url.URL) (string, string) {
|
func (tcs *testCredentialStore) Basic(*url.URL) (string, string) {
|
||||||
return tcs.username, tcs.password
|
return tcs.username, tcs.password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tcs *testCredentialStore) RefreshToken(u *url.URL, service string) string {
|
||||||
|
return tcs.refreshTokens[service]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tcs *testCredentialStore) SetRefreshToken(u *url.URL, service string, token string) {
|
||||||
|
if tcs.refreshTokens != nil {
|
||||||
|
tcs.refreshTokens[service] = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEndpointAuthorizeToken(t *testing.T) {
|
func TestEndpointAuthorizeToken(t *testing.T) {
|
||||||
service := "localhost.localdomain"
|
service := "localhost.localdomain"
|
||||||
repo1 := "some/registry"
|
repo1 := "some/registry"
|
||||||
|
@ -162,14 +173,11 @@ func TestEndpointAuthorizeToken(t *testing.T) {
|
||||||
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
badCheck := func(a string) bool {
|
e2, c2 := testServerWithAuth(m, authenicate, validCheck)
|
||||||
return a == "Bearer statictoken"
|
|
||||||
}
|
|
||||||
e2, c2 := testServerWithAuth(m, authenicate, badCheck)
|
|
||||||
defer c2()
|
defer c2()
|
||||||
|
|
||||||
challengeManager2 := NewSimpleChallengeManager()
|
challengeManager2 := NewSimpleChallengeManager()
|
||||||
versions, err = ping(challengeManager2, e+"/v2/", "x-multi-api-version")
|
versions, err = ping(challengeManager2, e2+"/v2/", "x-multi-api-version")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -199,6 +207,161 @@ func TestEndpointAuthorizeToken(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEndpointAuthorizeRefreshToken(t *testing.T) {
|
||||||
|
service := "localhost.localdomain"
|
||||||
|
repo1 := "some/registry"
|
||||||
|
repo2 := "other/registry"
|
||||||
|
scope1 := fmt.Sprintf("repository:%s:pull,push", repo1)
|
||||||
|
scope2 := fmt.Sprintf("repository:%s:pull,push", repo2)
|
||||||
|
refreshToken1 := "0123456790abcdef"
|
||||||
|
refreshToken2 := "0123456790fedcba"
|
||||||
|
tokenMap := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||||
|
{
|
||||||
|
Request: testutil.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Route: "/token",
|
||||||
|
Body: []byte(fmt.Sprintf("client_id=docker&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope1), service)),
|
||||||
|
},
|
||||||
|
Response: testutil.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken1)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// In the future this test may fail and require using basic auth to get a different refresh token
|
||||||
|
Request: testutil.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Route: "/token",
|
||||||
|
Body: []byte(fmt.Sprintf("client_id=docker&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken1, url.QueryEscape(scope2), service)),
|
||||||
|
},
|
||||||
|
Response: testutil.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: []byte(fmt.Sprintf(`{"access_token":"statictoken","refresh_token":"%s"}`, refreshToken2)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Request: testutil.Request{
|
||||||
|
Method: "POST",
|
||||||
|
Route: "/token",
|
||||||
|
Body: []byte(fmt.Sprintf("client_id=docker&grant_type=refresh_token&refresh_token=%s&scope=%s&service=%s", refreshToken2, url.QueryEscape(scope2), service)),
|
||||||
|
},
|
||||||
|
Response: testutil.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: []byte(`{"access_token":"badtoken","refresh_token":"%s"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
te, tc := testServer(tokenMap)
|
||||||
|
defer tc()
|
||||||
|
|
||||||
|
m := testutil.RequestResponseMap([]testutil.RequestResponseMapping{
|
||||||
|
{
|
||||||
|
Request: testutil.Request{
|
||||||
|
Method: "GET",
|
||||||
|
Route: "/v2/hello",
|
||||||
|
},
|
||||||
|
Response: testutil.Response{
|
||||||
|
StatusCode: http.StatusAccepted,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
authenicate := fmt.Sprintf("Bearer realm=%q,service=%q", te+"/token", service)
|
||||||
|
validCheck := func(a string) bool {
|
||||||
|
return a == "Bearer statictoken"
|
||||||
|
}
|
||||||
|
e, c := testServerWithAuth(m, authenicate, validCheck)
|
||||||
|
defer c()
|
||||||
|
|
||||||
|
challengeManager1 := NewSimpleChallengeManager()
|
||||||
|
versions, err := ping(challengeManager1, e+"/v2/", "x-api-version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(versions) != 1 {
|
||||||
|
t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
|
||||||
|
}
|
||||||
|
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||||
|
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||||
|
}
|
||||||
|
creds := &testCredentialStore{
|
||||||
|
refreshTokens: map[string]string{
|
||||||
|
service: refreshToken1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
transport1 := transport.NewTransport(nil, NewAuthorizer(challengeManager1, NewTokenHandler(nil, creds, repo1, "pull", "push")))
|
||||||
|
client := &http.Client{Transport: transport1}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", e+"/v2/hello", nil)
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error sending get request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
|
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusAccepted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try with refresh token setting
|
||||||
|
e2, c2 := testServerWithAuth(m, authenicate, validCheck)
|
||||||
|
defer c2()
|
||||||
|
|
||||||
|
challengeManager2 := NewSimpleChallengeManager()
|
||||||
|
versions, err = ping(challengeManager2, e2+"/v2/", "x-api-version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(versions) != 1 {
|
||||||
|
t.Fatalf("Unexpected version count: %d, expected 1", len(versions))
|
||||||
|
}
|
||||||
|
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||||
|
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport2 := transport.NewTransport(nil, NewAuthorizer(challengeManager2, NewTokenHandler(nil, creds, repo2, "pull", "push")))
|
||||||
|
client2 := &http.Client{Transport: transport2}
|
||||||
|
|
||||||
|
req, _ = http.NewRequest("GET", e2+"/v2/hello", nil)
|
||||||
|
resp, err = client2.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error sending get request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
|
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds.refreshTokens[service] != refreshToken2 {
|
||||||
|
t.Fatalf("Refresh token not set after change")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try with bad token
|
||||||
|
e3, c3 := testServerWithAuth(m, authenicate, validCheck)
|
||||||
|
defer c3()
|
||||||
|
|
||||||
|
challengeManager3 := NewSimpleChallengeManager()
|
||||||
|
versions, err = ping(challengeManager3, e3+"/v2/", "x-api-version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if check := (APIVersion{Type: "registry", Version: "2.0"}); versions[0] != check {
|
||||||
|
t.Fatalf("Unexpected api version: %#v, expected %#v", versions[0], check)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport3 := transport.NewTransport(nil, NewAuthorizer(challengeManager3, NewTokenHandler(nil, creds, repo2, "pull", "push")))
|
||||||
|
client3 := &http.Client{Transport: transport3}
|
||||||
|
|
||||||
|
req, _ = http.NewRequest("GET", e3+"/v2/hello", nil)
|
||||||
|
resp, err = client3.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error sending get request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("Unexpected status code: %d, expected %d", resp.StatusCode, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func basicAuth(username, password string) string {
|
func basicAuth(username, password string) string {
|
||||||
auth := username + ":" + password
|
auth := username + ":" + password
|
||||||
return base64.StdEncoding.EncodeToString([]byte(auth))
|
return base64.StdEncoding.EncodeToString([]byte(auth))
|
||||||
|
|
|
@ -25,6 +25,13 @@ func (c credentials) Basic(u *url.URL) (string, string) {
|
||||||
return up.username, up.password
|
return up.username, up.password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c credentials) RefreshToken(u *url.URL, service string) string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c credentials) SetRefreshToken(u *url.URL, service, token string) {
|
||||||
|
}
|
||||||
|
|
||||||
// configureAuth stores credentials for challenge responses
|
// configureAuth stores credentials for challenge responses
|
||||||
func configureAuth(username, password string) (auth.CredentialStore, error) {
|
func configureAuth(username, password string) (auth.CredentialStore, error) {
|
||||||
creds := map[string]userpass{
|
creds := map[string]userpass{
|
||||||
|
|
Loading…
Reference in a new issue