14 "github.com/Sirupsen/logrus"
15 "github.com/docker/distribution/reference"
16 "github.com/docker/docker/api/types"
17 registrytypes "github.com/docker/docker/api/types/registry"
18 "github.com/docker/docker/pkg/archive"
19 "github.com/docker/docker/pkg/stringid"
20 "github.com/docker/docker/registry"
21 "golang.org/x/net/context"
24 const secretRegistryToken = "mysecrettoken"
26 type tokenPassThruHandler struct {
29 shouldSend401 func(url string) bool
32 func (h *tokenPassThruHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
34 if strings.Contains(r.Header.Get("Authorization"), secretRegistryToken) {
35 logrus.Debug("Detected registry token in auth header")
38 if h.shouldSend401 == nil || h.shouldSend401(r.RequestURI) {
39 w.Header().Set("WWW-Authenticate", `Bearer realm="foorealm"`)
44 func testTokenPassThru(t *testing.T, ts *httptest.Server) {
45 tmp, err := testDirectory("")
49 defer os.RemoveAll(tmp)
51 uri, err := url.Parse(ts.URL)
53 t.Fatalf("could not parse url from test server: %v", err)
56 endpoint := registry.APIEndpoint{
64 n, _ := reference.ParseNormalizedNamed("testremotename")
65 repoInfo := ®istry.RepositoryInfo{
67 Index: ®istrytypes.IndexInfo{
75 imagePullConfig := &ImagePullConfig{
77 MetaHeaders: http.Header{},
78 AuthConfig: &types.AuthConfig{
79 RegistryToken: secretRegistryToken,
82 Schema2Types: ImageTypes,
84 puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
88 p := puller.(*v2Puller)
89 ctx := context.Background()
90 p.repo, _, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
95 logrus.Debug("About to pull")
96 // We expect it to fail, since we haven't mock'd the full registry exchange in our handler above
97 tag, _ := reference.WithTag(n, "tag_goes_here")
98 _ = p.pullV2Repository(ctx, tag)
101 func TestTokenPassThru(t *testing.T) {
102 handler := &tokenPassThruHandler{shouldSend401: func(url string) bool { return url == "/v2/" }}
103 ts := httptest.NewServer(handler)
106 testTokenPassThru(t, ts)
108 if !handler.reached {
109 t.Fatal("Handler not reached")
111 if !handler.gotToken {
112 t.Fatal("Failed to receive registry token")
116 func TestTokenPassThruDifferentHost(t *testing.T) {
117 handler := new(tokenPassThruHandler)
118 ts := httptest.NewServer(handler)
121 tsredirect := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
122 if r.RequestURI == "/v2/" {
123 w.Header().Set("WWW-Authenticate", `Bearer realm="foorealm"`)
127 http.Redirect(w, r, ts.URL+r.URL.Path, http.StatusMovedPermanently)
129 defer tsredirect.Close()
131 testTokenPassThru(t, tsredirect)
133 if !handler.reached {
134 t.Fatal("Handler not reached")
136 if handler.gotToken {
137 t.Fatal("Redirect should not forward Authorization header to another host")
141 // testDirectory creates a new temporary directory and returns its path.
142 // The contents of directory at path `templateDir` is copied into the
144 func testDirectory(templateDir string) (dir string, err error) {
145 testID := stringid.GenerateNonCryptoID()[:4]
146 prefix := fmt.Sprintf("docker-test%s-%s-", testID, getCallerName(2))
148 prefix = "docker-test-"
150 dir, err = ioutil.TempDir("", prefix)
151 if err = os.Remove(dir); err != nil {
154 if templateDir != "" {
155 if err = archive.NewDefaultArchiver().CopyWithTar(templateDir, dir); err != nil {
162 // getCallerName introspects the call stack and returns the name of the
163 // function `depth` levels down in the stack.
164 func getCallerName(depth int) string {
165 // Use the caller function name as a prefix.
166 // This helps trace temp directories back to their test.
167 pc, _, _, _ := runtime.Caller(depth + 1)
168 callerLongName := runtime.FuncForPC(pc).Name()
169 parts := strings.Split(callerLongName, ".")
170 callerShortName := parts[len(parts)-1]
171 return callerShortName