Merge pull request #6396 from AkihiroSuda/refresh-token
remotes/docker: allow fetching "refresh token" (aka "identity token", "offline token")
This commit is contained in:
		| @@ -73,6 +73,15 @@ type TokenOptions struct { | |||||||
| 	Scopes   []string | 	Scopes   []string | ||||||
| 	Username string | 	Username string | ||||||
| 	Secret   string | 	Secret   string | ||||||
|  |  | ||||||
|  | 	// FetchRefreshToken enables fetching a refresh token (aka "identity token", "offline token") along with the bearer token. | ||||||
|  | 	// | ||||||
|  | 	// For HTTP GET mode (FetchToken), FetchRefreshToken sets `offline_token=true` in the request. | ||||||
|  | 	// https://docs.docker.com/registry/spec/auth/token/#requesting-a-token | ||||||
|  | 	// | ||||||
|  | 	// For HTTP POST mode (FetchTokenWithOAuth), FetchRefreshToken sets `access_type=offline` in the request. | ||||||
|  | 	// https://docs.docker.com/registry/spec/auth/oauth/#getting-a-token | ||||||
|  | 	FetchRefreshToken bool | ||||||
| } | } | ||||||
|  |  | ||||||
| // OAuthTokenResponse is response from fetching token with a OAuth POST request | // OAuthTokenResponse is response from fetching token with a OAuth POST request | ||||||
| @@ -101,6 +110,9 @@ func FetchTokenWithOAuth(ctx context.Context, client *http.Client, headers http. | |||||||
| 		form.Set("username", to.Username) | 		form.Set("username", to.Username) | ||||||
| 		form.Set("password", to.Secret) | 		form.Set("password", to.Secret) | ||||||
| 	} | 	} | ||||||
|  | 	if to.FetchRefreshToken { | ||||||
|  | 		form.Set("access_type", "offline") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	req, err := http.NewRequest("POST", to.Realm, strings.NewReader(form.Encode())) | 	req, err := http.NewRequest("POST", to.Realm, strings.NewReader(form.Encode())) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -175,6 +187,10 @@ func FetchToken(ctx context.Context, client *http.Client, headers http.Header, t | |||||||
| 		req.SetBasicAuth(to.Username, to.Secret) | 		req.SetBasicAuth(to.Username, to.Secret) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if to.FetchRefreshToken { | ||||||
|  | 		reqParams.Add("offline_token", "true") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	req.URL.RawQuery = reqParams.Encode() | 	req.URL.RawQuery = reqParams.Encode() | ||||||
|  |  | ||||||
| 	resp, err := ctxhttp.Do(ctx, client, req) | 	resp, err := ctxhttp.Do(ctx, client, req) | ||||||
|   | |||||||
| @@ -37,10 +37,12 @@ type dockerAuthorizer struct { | |||||||
|  |  | ||||||
| 	client *http.Client | 	client *http.Client | ||||||
| 	header http.Header | 	header http.Header | ||||||
| 	mu     sync.Mutex | 	mu     sync.RWMutex | ||||||
|  |  | ||||||
| 	// indexed by host name | 	// indexed by host name | ||||||
| 	handlers map[string]*authHandler | 	handlers map[string]*authHandler | ||||||
|  |  | ||||||
|  | 	onFetchRefreshToken OnFetchRefreshToken | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewAuthorizer creates a Docker authorizer using the provided function to | // NewAuthorizer creates a Docker authorizer using the provided function to | ||||||
| @@ -51,9 +53,10 @@ func NewAuthorizer(client *http.Client, f func(string) (string, string, error)) | |||||||
| } | } | ||||||
|  |  | ||||||
| type authorizerConfig struct { | type authorizerConfig struct { | ||||||
| 	credentials func(string) (string, string, error) | 	credentials         func(string) (string, string, error) | ||||||
| 	client      *http.Client | 	client              *http.Client | ||||||
| 	header      http.Header | 	header              http.Header | ||||||
|  | 	onFetchRefreshToken OnFetchRefreshToken | ||||||
| } | } | ||||||
|  |  | ||||||
| // AuthorizerOpt configures an authorizer | // AuthorizerOpt configures an authorizer | ||||||
| @@ -80,6 +83,16 @@ func WithAuthHeader(hdr http.Header) AuthorizerOpt { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // OnFetchRefreshToken is called on fetching request token. | ||||||
|  | type OnFetchRefreshToken func(ctx context.Context, refreshToken string, req *http.Request) | ||||||
|  |  | ||||||
|  | // WithFetchRefreshToken enables fetching "refresh token" (aka "identity token", "offline token"). | ||||||
|  | func WithFetchRefreshToken(f OnFetchRefreshToken) AuthorizerOpt { | ||||||
|  | 	return func(opt *authorizerConfig) { | ||||||
|  | 		opt.onFetchRefreshToken = f | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| // NewDockerAuthorizer creates an authorizer using Docker's registry | // NewDockerAuthorizer creates an authorizer using Docker's registry | ||||||
| // authentication spec. | // authentication spec. | ||||||
| // See https://docs.docker.com/registry/spec/auth/ | // See https://docs.docker.com/registry/spec/auth/ | ||||||
| @@ -94,10 +107,11 @@ func NewDockerAuthorizer(opts ...AuthorizerOpt) Authorizer { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return &dockerAuthorizer{ | 	return &dockerAuthorizer{ | ||||||
| 		credentials: ao.credentials, | 		credentials:         ao.credentials, | ||||||
| 		client:      ao.client, | 		client:              ao.client, | ||||||
| 		header:      ao.header, | 		header:              ao.header, | ||||||
| 		handlers:    make(map[string]*authHandler), | 		handlers:            make(map[string]*authHandler), | ||||||
|  | 		onFetchRefreshToken: ao.onFetchRefreshToken, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -109,12 +123,21 @@ func (a *dockerAuthorizer) Authorize(ctx context.Context, req *http.Request) err | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	auth, err := ah.authorize(ctx) | 	auth, refreshToken, err := ah.authorize(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	req.Header.Set("Authorization", auth) | 	req.Header.Set("Authorization", auth) | ||||||
|  |  | ||||||
|  | 	if refreshToken != "" { | ||||||
|  | 		a.mu.RLock() | ||||||
|  | 		onFetchRefreshToken := a.onFetchRefreshToken | ||||||
|  | 		a.mu.RUnlock() | ||||||
|  | 		if onFetchRefreshToken != nil { | ||||||
|  | 			onFetchRefreshToken(ctx, refreshToken, req) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -161,6 +184,7 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
|  | 			common.FetchRefreshToken = a.onFetchRefreshToken != nil | ||||||
|  |  | ||||||
| 			a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common) | 			a.handlers[host] = newAuthHandler(a.client, a.header, c.Scheme, common) | ||||||
| 			return nil | 			return nil | ||||||
| @@ -187,8 +211,9 @@ func (a *dockerAuthorizer) AddResponses(ctx context.Context, responses []*http.R | |||||||
| // authResult is used to control limit rate. | // authResult is used to control limit rate. | ||||||
| type authResult struct { | type authResult struct { | ||||||
| 	sync.WaitGroup | 	sync.WaitGroup | ||||||
| 	token string | 	token        string | ||||||
| 	err   error | 	refreshToken string | ||||||
|  | 	err          error | ||||||
| } | } | ||||||
|  |  | ||||||
| // authHandler is used to handle auth request per registry server. | // authHandler is used to handle auth request per registry server. | ||||||
| @@ -220,29 +245,29 @@ func newAuthHandler(client *http.Client, hdr http.Header, scheme auth.Authentica | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ah *authHandler) authorize(ctx context.Context) (string, error) { | func (ah *authHandler) authorize(ctx context.Context) (string, string, error) { | ||||||
| 	switch ah.scheme { | 	switch ah.scheme { | ||||||
| 	case auth.BasicAuth: | 	case auth.BasicAuth: | ||||||
| 		return ah.doBasicAuth(ctx) | 		return ah.doBasicAuth(ctx) | ||||||
| 	case auth.BearerAuth: | 	case auth.BearerAuth: | ||||||
| 		return ah.doBearerAuth(ctx) | 		return ah.doBearerAuth(ctx) | ||||||
| 	default: | 	default: | ||||||
| 		return "", errors.Wrapf(errdefs.ErrNotImplemented, "failed to find supported auth scheme: %s", string(ah.scheme)) | 		return "", "", errors.Wrapf(errdefs.ErrNotImplemented, "failed to find supported auth scheme: %s", string(ah.scheme)) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ah *authHandler) doBasicAuth(ctx context.Context) (string, error) { | func (ah *authHandler) doBasicAuth(ctx context.Context) (string, string, error) { | ||||||
| 	username, secret := ah.common.Username, ah.common.Secret | 	username, secret := ah.common.Username, ah.common.Secret | ||||||
|  |  | ||||||
| 	if username == "" || secret == "" { | 	if username == "" || secret == "" { | ||||||
| 		return "", fmt.Errorf("failed to handle basic auth because missing username or secret") | 		return "", "", fmt.Errorf("failed to handle basic auth because missing username or secret") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + secret)) | 	auth := base64.StdEncoding.EncodeToString([]byte(username + ":" + secret)) | ||||||
| 	return fmt.Sprintf("Basic %s", auth), nil | 	return fmt.Sprintf("Basic %s", auth), "", nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (ah *authHandler) doBearerAuth(ctx context.Context) (token string, err error) { | func (ah *authHandler) doBearerAuth(ctx context.Context) (token, refreshToken string, err error) { | ||||||
| 	// copy common tokenOptions | 	// copy common tokenOptions | ||||||
| 	to := ah.common | 	to := ah.common | ||||||
|  |  | ||||||
| @@ -255,7 +280,7 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token string, err erro | |||||||
| 	if r, exist := ah.scopedTokens[scoped]; exist { | 	if r, exist := ah.scopedTokens[scoped]; exist { | ||||||
| 		ah.Unlock() | 		ah.Unlock() | ||||||
| 		r.Wait() | 		r.Wait() | ||||||
| 		return r.token, r.err | 		return r.token, r.refreshToken, r.err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// only one fetch token job | 	// only one fetch token job | ||||||
| @@ -266,7 +291,7 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token string, err erro | |||||||
|  |  | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		token = fmt.Sprintf("Bearer %s", token) | 		token = fmt.Sprintf("Bearer %s", token) | ||||||
| 		r.token, r.err = token, err | 		r.token, r.refreshToken, r.err = token, refreshToken, err | ||||||
| 		r.Done() | 		r.Done() | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| @@ -287,25 +312,25 @@ func (ah *authHandler) doBearerAuth(ctx context.Context) (token string, err erro | |||||||
| 				if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 { | 				if (errStatus.StatusCode == 405 && to.Username != "") || errStatus.StatusCode == 404 || errStatus.StatusCode == 401 { | ||||||
| 					resp, err := auth.FetchToken(ctx, ah.client, ah.header, to) | 					resp, err := auth.FetchToken(ctx, ah.client, ah.header, to) | ||||||
| 					if err != nil { | 					if err != nil { | ||||||
| 						return "", err | 						return "", "", err | ||||||
| 					} | 					} | ||||||
| 					return resp.Token, nil | 					return resp.Token, resp.RefreshToken, nil | ||||||
| 				} | 				} | ||||||
| 				log.G(ctx).WithFields(logrus.Fields{ | 				log.G(ctx).WithFields(logrus.Fields{ | ||||||
| 					"status": errStatus.Status, | 					"status": errStatus.Status, | ||||||
| 					"body":   string(errStatus.Body), | 					"body":   string(errStatus.Body), | ||||||
| 				}).Debugf("token request failed") | 				}).Debugf("token request failed") | ||||||
| 			} | 			} | ||||||
| 			return "", err | 			return "", "", err | ||||||
| 		} | 		} | ||||||
| 		return resp.AccessToken, nil | 		return resp.AccessToken, resp.RefreshToken, nil | ||||||
| 	} | 	} | ||||||
| 	// do request anonymously | 	// do request anonymously | ||||||
| 	resp, err := auth.FetchToken(ctx, ah.client, ah.header, to) | 	resp, err := auth.FetchToken(ctx, ah.client, ah.header, to) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", errors.Wrap(err, "failed to fetch anonymous token") | 		return "", "", errors.Wrap(err, "failed to fetch anonymous token") | ||||||
| 	} | 	} | ||||||
| 	return resp.Token, nil | 	return resp.Token, resp.RefreshToken, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func invalidAuthorization(c auth.Challenge, responses []*http.Response) error { | func invalidAuthorization(c auth.Challenge, responses []*http.Response) error { | ||||||
|   | |||||||
| @@ -63,7 +63,8 @@ type HostOptions struct { | |||||||
| 	DefaultTLS    *tls.Config | 	DefaultTLS    *tls.Config | ||||||
| 	DefaultScheme string | 	DefaultScheme string | ||||||
| 	// UpdateClient will be called after creating http.Client object, so clients can provide extra configuration | 	// UpdateClient will be called after creating http.Client object, so clients can provide extra configuration | ||||||
| 	UpdateClient UpdateClientFunc | 	UpdateClient   UpdateClientFunc | ||||||
|  | 	AuthorizerOpts []docker.AuthorizerOpt | ||||||
| } | } | ||||||
|  |  | ||||||
| // ConfigureHosts creates a registry hosts function from the provided | // ConfigureHosts creates a registry hosts function from the provided | ||||||
| @@ -143,6 +144,7 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos | |||||||
| 		if options.Credentials != nil { | 		if options.Credentials != nil { | ||||||
| 			authOpts = append(authOpts, docker.WithAuthCreds(options.Credentials)) | 			authOpts = append(authOpts, docker.WithAuthCreds(options.Credentials)) | ||||||
| 		} | 		} | ||||||
|  | 		authOpts = append(authOpts, options.AuthorizerOpts...) | ||||||
| 		authorizer := docker.NewDockerAuthorizer(authOpts...) | 		authorizer := docker.NewDockerAuthorizer(authOpts...) | ||||||
|  |  | ||||||
| 		rhosts := make([]docker.RegistryHost, len(hosts)) | 		rhosts := make([]docker.RegistryHost, len(hosts)) | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ import ( | |||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/containerd/containerd/remotes" | 	"github.com/containerd/containerd/remotes" | ||||||
|  | 	"github.com/containerd/containerd/remotes/docker/auth" | ||||||
| 	digest "github.com/opencontainers/go-digest" | 	digest "github.com/opencontainers/go-digest" | ||||||
| 	specs "github.com/opencontainers/image-spec/specs-go" | 	specs "github.com/opencontainers/image-spec/specs-go" | ||||||
| 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | 	ocispec "github.com/opencontainers/image-spec/specs-go/v1" | ||||||
| @@ -137,6 +138,31 @@ func TestRefreshTokenResolver(t *testing.T) { | |||||||
| 	runBasicTest(t, "testname", withTokenServer(th, creds)) | 	runBasicTest(t, "testname", withTokenServer(th, creds)) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestFetchRefreshToken(t *testing.T) { | ||||||
|  | 	f := func(t *testing.T, disablePOST bool) { | ||||||
|  | 		name := "testname" | ||||||
|  | 		if disablePOST { | ||||||
|  | 			name += "-disable-post" | ||||||
|  | 		} | ||||||
|  | 		var fetchedRefreshToken string | ||||||
|  | 		onFetchRefreshToken := func(ctx context.Context, refreshToken string, req *http.Request) { | ||||||
|  | 			fetchedRefreshToken = refreshToken | ||||||
|  | 		} | ||||||
|  | 		srv := newRefreshTokenServer(t, name, disablePOST, onFetchRefreshToken) | ||||||
|  | 		runBasicTest(t, name, srv.BasicTestFunc()) | ||||||
|  | 		if fetchedRefreshToken != srv.RefreshToken { | ||||||
|  | 			t.Errorf("unexpected refresh token: got %q", fetchedRefreshToken) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	t.Run("POST", func(t *testing.T) { | ||||||
|  | 		f(t, false) | ||||||
|  | 	}) | ||||||
|  | 	t.Run("GET", func(t *testing.T) { | ||||||
|  | 		f(t, true) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestPostBasicAuthTokenResolver(t *testing.T) { | func TestPostBasicAuthTokenResolver(t *testing.T) { | ||||||
| 	th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | 	th := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||||
| 		if r.Method != http.MethodPost { | 		if r.Method != http.MethodPost { | ||||||
| @@ -658,3 +684,131 @@ func (m testManifest) RegisterHandler(r *http.ServeMux, name string) { | |||||||
| 		r.Handle(fmt.Sprintf("/v2/%s/blobs/%s", name, c.Digest()), c) | 		r.Handle(fmt.Sprintf("/v2/%s/blobs/%s", name, c.Digest()), c) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func newRefreshTokenServer(t testing.TB, name string, disablePOST bool, onFetchRefreshToken OnFetchRefreshToken) *refreshTokenServer { | ||||||
|  | 	return &refreshTokenServer{ | ||||||
|  | 		T:                   t, | ||||||
|  | 		Name:                name, | ||||||
|  | 		DisablePOST:         disablePOST, | ||||||
|  | 		OnFetchRefreshToken: onFetchRefreshToken, | ||||||
|  | 		AccessToken:         "testAccessToken-" + name, | ||||||
|  | 		RefreshToken:        "testRefreshToken-" + name, | ||||||
|  | 		Username:            "testUser-" + name, | ||||||
|  | 		Password:            "testPassword-" + name, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type refreshTokenServer struct { | ||||||
|  | 	T                   testing.TB | ||||||
|  | 	Name                string | ||||||
|  | 	DisablePOST         bool | ||||||
|  | 	OnFetchRefreshToken OnFetchRefreshToken | ||||||
|  | 	AccessToken         string | ||||||
|  | 	RefreshToken        string | ||||||
|  | 	Username            string | ||||||
|  | 	Password            string | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (srv *refreshTokenServer) isValidAuthorizationHeader(s string) bool { | ||||||
|  | 	fields := strings.Fields(s) | ||||||
|  | 	return len(fields) == 2 && strings.ToLower(fields[0]) == "bearer" && (fields[1] == srv.RefreshToken || fields[1] == srv.AccessToken) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (srv *refreshTokenServer) BasicTestFunc() func(h http.Handler) (string, ResolverOptions, func()) { | ||||||
|  | 	t := srv.T | ||||||
|  | 	return func(h http.Handler) (string, ResolverOptions, func()) { | ||||||
|  | 		wrapped := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { | ||||||
|  | 			if r.URL.Path != "/token" { | ||||||
|  | 				if !srv.isValidAuthorizationHeader(r.Header.Get("Authorization")) { | ||||||
|  | 					realm := fmt.Sprintf("https://%s/token", r.Host) | ||||||
|  | 					wwwAuthenticateHeader := fmt.Sprintf("Bearer realm=%q,service=registry,scope=\"repository:%s:pull\"", realm, srv.Name) | ||||||
|  | 					rw.Header().Set("WWW-Authenticate", wwwAuthenticateHeader) | ||||||
|  | 					rw.WriteHeader(http.StatusUnauthorized) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				h.ServeHTTP(rw, r) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			switch r.Method { | ||||||
|  | 			case http.MethodGet: // https://docs.docker.com/registry/spec/auth/token/#requesting-a-token | ||||||
|  | 				u, p, ok := r.BasicAuth() | ||||||
|  | 				if !ok || u != srv.Username || p != srv.Password { | ||||||
|  | 					rw.WriteHeader(http.StatusForbidden) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				var resp auth.FetchTokenResponse | ||||||
|  | 				resp.Token = srv.AccessToken | ||||||
|  | 				resp.AccessToken = srv.AccessToken // alias of Token | ||||||
|  | 				query := r.URL.Query() | ||||||
|  | 				switch query.Get("offline_token") { | ||||||
|  | 				case "true": | ||||||
|  | 					resp.RefreshToken = srv.RefreshToken | ||||||
|  | 				case "false", "": | ||||||
|  | 				default: | ||||||
|  | 					rw.WriteHeader(http.StatusBadRequest) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				b, err := json.Marshal(resp) | ||||||
|  | 				if err != nil { | ||||||
|  | 					rw.WriteHeader(http.StatusInternalServerError) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				rw.WriteHeader(http.StatusOK) | ||||||
|  | 				rw.Header().Set("Content-Type", "application/json") | ||||||
|  | 				t.Logf("GET mode: returning JSON %q, for query %+v", string(b), query) | ||||||
|  | 				rw.Write(b) | ||||||
|  | 			case http.MethodPost: // https://docs.docker.com/registry/spec/auth/oauth/#getting-a-token | ||||||
|  | 				if srv.DisablePOST { | ||||||
|  | 					rw.WriteHeader(http.StatusMethodNotAllowed) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				r.ParseForm() | ||||||
|  | 				pf := r.PostForm | ||||||
|  | 				if pf.Get("grant_type") != "password" { | ||||||
|  | 					rw.WriteHeader(http.StatusBadRequest) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				if pf.Get("username") != srv.Username || pf.Get("password") != srv.Password { | ||||||
|  | 					rw.WriteHeader(http.StatusForbidden) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				var resp auth.OAuthTokenResponse | ||||||
|  | 				resp.AccessToken = srv.AccessToken | ||||||
|  | 				switch pf.Get("access_type") { | ||||||
|  | 				case "offline": | ||||||
|  | 					resp.RefreshToken = srv.RefreshToken | ||||||
|  | 				case "online", "": | ||||||
|  | 				default: | ||||||
|  | 					rw.WriteHeader(http.StatusBadRequest) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				b, err := json.Marshal(resp) | ||||||
|  | 				if err != nil { | ||||||
|  | 					rw.WriteHeader(http.StatusInternalServerError) | ||||||
|  | 					return | ||||||
|  | 				} | ||||||
|  | 				rw.WriteHeader(http.StatusOK) | ||||||
|  | 				rw.Header().Set("Content-Type", "application/json") | ||||||
|  | 				t.Logf("POST mode: returning JSON %q, for form %+v", string(b), pf) | ||||||
|  | 				rw.Write(b) | ||||||
|  | 			default: | ||||||
|  | 				rw.WriteHeader(http.StatusMethodNotAllowed) | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  |  | ||||||
|  | 		base, options, close := tlsServer(wrapped) | ||||||
|  | 		authorizer := NewDockerAuthorizer( | ||||||
|  | 			WithAuthClient(options.Client), | ||||||
|  | 			WithAuthCreds(func(string) (string, string, error) { | ||||||
|  | 				return srv.Username, srv.Password, nil | ||||||
|  | 			}), | ||||||
|  | 			WithFetchRefreshToken(srv.OnFetchRefreshToken), | ||||||
|  | 		) | ||||||
|  | 		options.Hosts = ConfigureDefaultRegistries( | ||||||
|  | 			WithClient(options.Client), | ||||||
|  | 			WithAuthorizer(authorizer), | ||||||
|  | 		) | ||||||
|  | 		return base, options, close | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Derek McGowan
					Derek McGowan