mirror of
https://github.com/genuinetools/reg.git
synced 2024-05-20 12:08:33 -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
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -12,6 +13,9 @@ var (
|
||||||
bearerRegex = regexp.MustCompile(
|
bearerRegex = regexp.MustCompile(
|
||||||
`^\s*Bearer\s+(.*)$`)
|
`^\s*Bearer\s+(.*)$`)
|
||||||
basicRegex = regexp.MustCompile(`^\s*Basic\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) {
|
func parseAuthHeader(header http.Header) (*authService, error) {
|
||||||
|
@ -25,7 +29,7 @@ func parseAuthHeader(header http.Header) (*authService, error) {
|
||||||
|
|
||||||
func parseChallenge(challengeHeader string) (*authService, error) {
|
func parseChallenge(challengeHeader string) (*authService, error) {
|
||||||
if basicRegex.MatchString(challengeHeader) {
|
if basicRegex.MatchString(challengeHeader) {
|
||||||
return nil, fmt.Errorf("basic auth required")
|
return nil, ErrBasicAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
match := bearerRegex.FindAllStringSubmatch(challengeHeader, -1)
|
match := bearerRegex.FindAllStringSubmatch(challengeHeader, -1)
|
||||||
|
|
|
@ -47,6 +47,14 @@ func TestParseChallenge(t *testing.T) {
|
||||||
scope: []string{"repository:chrome:pull"},
|
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 {
|
for _, tc := range challengeHeaderCases {
|
||||||
|
@ -54,7 +62,7 @@ func TestParseChallenge(t *testing.T) {
|
||||||
if err != nil && !strings.Contains(err.Error(), tc.errorString) {
|
if err != nil && !strings.Contains(err.Error(), tc.errorString) {
|
||||||
t.Fatalf("expected error to contain %v, got %s", tc.errorString, err)
|
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)
|
t.Fatalf("got %v, expected %v", val, tc.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenTransport defines the data structure for authentication via tokens.
|
// TokenTransport defines the data structure for authentication via tokens.
|
||||||
|
@ -121,7 +120,8 @@ func isTokenDemand(resp *http.Response) (*authService, error) {
|
||||||
return parseAuthHeader(resp.Header)
|
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) {
|
func (r *Registry) Token(url string) (string, error) {
|
||||||
r.Logf("registry.token url=%s", url)
|
r.Logf("registry.token url=%s", url)
|
||||||
|
|
||||||
|
@ -189,16 +189,12 @@ func (r *Registry) Headers(uri string) (map[string]string, error) {
|
||||||
// Get the token.
|
// Get the token.
|
||||||
token, err := r.Token(uri)
|
token, err := r.Token(uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we get an error here of type: malformed auth challenge header: 'Basic realm="Registry Realm"'
|
if err == ErrBasicAuth {
|
||||||
// We need to use basic auth for the registry.
|
// If we couldn't get a token because the server requires basic auth, just return basic auth headers.
|
||||||
if !strings.Contains(err.Error(), `malformed auth challenge header: 'Basic realm="Registry Realm"'`) && !strings.Contains(err.Error(), "basic auth required") {
|
return map[string]string{
|
||||||
return nil, err
|
"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 {
|
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