mirror of
https://github.com/genuinetools/reg.git
synced 2024-05-10 00:38:31 -04:00
Use a distinguished error value for basic auth. (#120)
Rather than returning an error that requires pattern-matching from `parseChallenge` when the challenge header requires basic authentication, return a distinguished error value. This makes checking for this error a bit easier. This commit also updates the check in `r.Headers` to use the new error value and adds a couple of regression tests.
This commit is contained in:
parent
8e3ba57e3e
commit
65b2c0329d
|
@ -1,6 +1,7 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -12,6 +13,9 @@ var (
|
|||
bearerRegex = regexp.MustCompile(
|
||||
`^\s*Bearer\s+(.*)$`)
|
||||
basicRegex = regexp.MustCompile(`^\s*Basic\s+.*$`)
|
||||
|
||||
// ErrBasicAuth indicates that the repository requires basic rather than token authentication.
|
||||
ErrBasicAuth = errors.New("basic auth required")
|
||||
)
|
||||
|
||||
func parseAuthHeader(header http.Header) (*authService, error) {
|
||||
|
@ -25,7 +29,7 @@ func parseAuthHeader(header http.Header) (*authService, error) {
|
|||
|
||||
func parseChallenge(challengeHeader string) (*authService, error) {
|
||||
if basicRegex.MatchString(challengeHeader) {
|
||||
return nil, fmt.Errorf("basic auth required")
|
||||
return nil, ErrBasicAuth
|
||||
}
|
||||
|
||||
match := bearerRegex.FindAllStringSubmatch(challengeHeader, -1)
|
||||
|
|
|
@ -47,6 +47,14 @@ func TestParseChallenge(t *testing.T) {
|
|||
scope: []string{"repository:chrome:pull"},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: `Basic realm="https://r.j3ss.co/auth",service="Docker registry"`,
|
||||
errorString: "basic auth required",
|
||||
},
|
||||
{
|
||||
header: `Basic realm="Registry Realm",service="Docker registry"`,
|
||||
errorString: "basic auth required",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range challengeHeaderCases {
|
||||
|
@ -54,7 +62,7 @@ func TestParseChallenge(t *testing.T) {
|
|||
if err != nil && !strings.Contains(err.Error(), tc.errorString) {
|
||||
t.Fatalf("expected error to contain %v, got %s", tc.errorString, err)
|
||||
}
|
||||
if !tc.value.equalTo(val) {
|
||||
if err == nil && !tc.value.equalTo(val) {
|
||||
t.Fatalf("got %v, expected %v", val, tc.value)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TokenTransport defines the data structure for authentication via tokens.
|
||||
|
@ -121,7 +120,8 @@ func isTokenDemand(resp *http.Response) (*authService, error) {
|
|||
return parseAuthHeader(resp.Header)
|
||||
}
|
||||
|
||||
// Token returns the required token for the specific resource url.
|
||||
// Token returns the required token for the specific resource url. If the registry requires basic authentication, this
|
||||
// function returns ErrBasicAuth.
|
||||
func (r *Registry) Token(url string) (string, error) {
|
||||
r.Logf("registry.token url=%s", url)
|
||||
|
||||
|
@ -189,16 +189,12 @@ func (r *Registry) Headers(uri string) (map[string]string, error) {
|
|||
// Get the token.
|
||||
token, err := r.Token(uri)
|
||||
if err != nil {
|
||||
// If we get an error here of type: malformed auth challenge header: 'Basic realm="Registry Realm"'
|
||||
// We need to use basic auth for the registry.
|
||||
if !strings.Contains(err.Error(), `malformed auth challenge header: 'Basic realm="Registry Realm"'`) && !strings.Contains(err.Error(), "basic auth required") {
|
||||
return nil, err
|
||||
if err == ErrBasicAuth {
|
||||
// If we couldn't get a token because the server requires basic auth, just return basic auth headers.
|
||||
return map[string]string{
|
||||
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Return basic auth headers.
|
||||
return map[string]string{
|
||||
"Authorization": fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(r.Username+":"+r.Password))),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if len(token) < 1 {
|
||||
|
|
38
registry/tokentransport_test.go
Normal file
38
registry/tokentransport_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestErrBasicAuth(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
w.Header().Set("www-authenticate", `Basic realm="Registry Realm",service="Docker registry"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
authConfig := types.AuthConfig{
|
||||
Username: "j3ss",
|
||||
Password: "ss3j",
|
||||
ServerAddress: ts.URL,
|
||||
}
|
||||
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 != ErrBasicAuth {
|
||||
t.Fatalf("expected ErrBasicAuth getting token, got %v", err)
|
||||
}
|
||||
if token != "" {
|
||||
t.Fatalf("expected empty token, got %v", err)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue