Merge pull request #751 from dotcloud/660-auth_client-feature

* Registry: Move auth to the client
This commit is contained in:
Guillaume J. Charmes 2013-06-13 11:52:40 -07:00
commit 8085754507
12 changed files with 3188 additions and 1190 deletions

53
api.go
View File

@ -13,7 +13,7 @@ import (
"strings" "strings"
) )
const APIVERSION = 1.1 const APIVERSION = 1.2
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
conn, _, err := w.(http.Hijacker).Hijack() conn, _, err := w.(http.Hijacker).Hijack()
@ -71,8 +71,10 @@ func getBoolParam(value string) (bool, error) {
} }
func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// FIXME: Handle multiple login at once if version > 1.1 {
// FIXME: return specific error code if config file missing? w.WriteHeader(http.StatusNotFound)
return nil
}
authConfig, err := auth.LoadConfig(srv.runtime.root) authConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil { if err != nil {
if err != auth.ErrConfigFileMissing { if err != auth.ErrConfigFileMissing {
@ -89,29 +91,34 @@ func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Reques
} }
func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
// FIXME: Handle multiple login at once authConfig := &auth.AuthConfig{}
config := &auth.AuthConfig{} err := json.NewDecoder(r.Body).Decode(authConfig)
if err := json.NewDecoder(r.Body).Decode(config); err != nil { if err != nil {
return err return err
} }
status := ""
authConfig, err := auth.LoadConfig(srv.runtime.root) if version > 1.1 {
status, err = auth.Login(authConfig, false)
if err != nil {
return err
}
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil { if err != nil {
if err != auth.ErrConfigFileMissing { if err != auth.ErrConfigFileMissing {
return err return err
} }
authConfig = &auth.AuthConfig{}
} }
if config.Username == authConfig.Username { if authConfig.Username == localAuthConfig.Username {
config.Password = authConfig.Password authConfig.Password = localAuthConfig.Password
} }
newAuthConfig := auth.NewAuthConfig(config.Username, config.Password, config.Email, srv.runtime.root) newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
status, err := auth.Login(newAuthConfig) status, err = auth.Login(newAuthConfig, true)
if err != nil { if err != nil {
return err return err
} }
}
if status != "" { if status != "" {
b, err := json.Marshal(&APIAuth{Status: status}) b, err := json.Marshal(&APIAuth{Status: status})
if err != nil { if err != nil {
@ -322,7 +329,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
sf := utils.NewStreamFormatter(version > 1.0) sf := utils.NewStreamFormatter(version > 1.0)
if image != "" { //pull if image != "" { //pull
registry := r.Form.Get("registry") registry := r.Form.Get("registry")
if err := srv.ImagePull(image, tag, registry, w, sf); err != nil { if err := srv.ImagePull(image, tag, registry, w, sf, &auth.AuthConfig{}); err != nil {
if sf.Used() { if sf.Used() {
w.Write(sf.FormatError(err)) w.Write(sf.FormatError(err))
return nil return nil
@ -390,6 +397,18 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht
} }
func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
authConfig := &auth.AuthConfig{}
if version > 1.1 {
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
return err
}
} else {
localAuthConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil && err != auth.ErrConfigFileMissing {
return err
}
authConfig = localAuthConfig
}
if err := parseForm(r); err != nil { if err := parseForm(r); err != nil {
return err return err
} }
@ -403,7 +422,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
} }
sf := utils.NewStreamFormatter(version > 1.0) sf := utils.NewStreamFormatter(version > 1.0)
if err := srv.ImagePush(name, registry, w, sf); err != nil { if err := srv.ImagePush(name, registry, w, sf, authConfig); err != nil {
if sf.Used() { if sf.Used() {
w.Write(sf.FormatError(err)) w.Write(sf.FormatError(err))
return nil return nil
@ -490,7 +509,7 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
return fmt.Errorf("Missing parameter") return fmt.Errorf("Missing parameter")
} }
name := vars["name"] name := vars["name"]
imgs, err := srv.ImageDelete(name, version > 1.0) imgs, err := srv.ImageDelete(name, version > 1.1)
if err != nil { if err != nil {
return err return err
} }

View File

@ -6,7 +6,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils" "github.com/dotcloud/docker/utils"
"io" "io"
"net" "net"
@ -18,7 +17,7 @@ import (
"time" "time"
) )
func TestGetAuth(t *testing.T) { func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime() runtime, err := newTestRuntime()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -54,12 +53,6 @@ func TestGetAuth(t *testing.T) {
if r.Code != http.StatusOK && r.Code != 0 { if r.Code != http.StatusOK && r.Code != 0 {
t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code) t.Fatalf("%d OK or 0 expected, received %d\n", http.StatusOK, r.Code)
} }
newAuthConfig := registry.NewRegistry(runtime.root).GetAuthConfig(false)
if newAuthConfig.Username != authConfig.Username ||
newAuthConfig.Email != authConfig.Email {
t.Fatalf("The auth configuration hasn't been set correctly")
}
} }
func TestGetVersion(t *testing.T) { func TestGetVersion(t *testing.T) {
@ -494,40 +487,6 @@ func TestGetContainersByName(t *testing.T) {
} }
} }
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
}
defer nuke(runtime)
srv := &Server{
runtime: runtime,
}
config := &auth.AuthConfig{
Username: "utest",
Email: "utest@yopmail.com",
}
authStr := auth.EncodeAuth(config)
auth.SaveConfig(runtime.root, authStr, config.Email)
r := httptest.NewRecorder()
if err := getAuth(srv, APIVERSION, r, nil, nil); err != nil {
t.Fatal(err)
}
authConfig := &auth.AuthConfig{}
if err := json.Unmarshal(r.Body.Bytes(), authConfig); err != nil {
t.Fatal(err)
}
if authConfig.Username != config.Username || authConfig.Email != config.Email {
t.Errorf("The retrieve auth mismatch with the one set.")
}
}
func TestPostCommit(t *testing.T) { func TestPostCommit(t *testing.T) {
runtime, err := newTestRuntime() runtime, err := newTestRuntime()
if err != nil { if err != nil {

View File

@ -48,7 +48,7 @@ func IndexServerAddress() string {
} }
// create a base64 encoded auth string to store in config // create a base64 encoded auth string to store in config
func EncodeAuth(authConfig *AuthConfig) string { func encodeAuth(authConfig *AuthConfig) string {
authStr := authConfig.Username + ":" + authConfig.Password authStr := authConfig.Username + ":" + authConfig.Password
msg := []byte(authStr) msg := []byte(authStr)
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
@ -57,7 +57,7 @@ func EncodeAuth(authConfig *AuthConfig) string {
} }
// decode the auth string // decode the auth string
func DecodeAuth(authStr string) (*AuthConfig, error) { func decodeAuth(authStr string) (*AuthConfig, error) {
decLen := base64.StdEncoding.DecodedLen(len(authStr)) decLen := base64.StdEncoding.DecodedLen(len(authStr))
decoded := make([]byte, decLen) decoded := make([]byte, decLen)
authByte := []byte(authStr) authByte := []byte(authStr)
@ -82,7 +82,7 @@ func DecodeAuth(authStr string) (*AuthConfig, error) {
func LoadConfig(rootPath string) (*AuthConfig, error) { func LoadConfig(rootPath string) (*AuthConfig, error) {
confFile := path.Join(rootPath, CONFIGFILE) confFile := path.Join(rootPath, CONFIGFILE)
if _, err := os.Stat(confFile); err != nil { if _, err := os.Stat(confFile); err != nil {
return nil, ErrConfigFileMissing return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing
} }
b, err := ioutil.ReadFile(confFile) b, err := ioutil.ReadFile(confFile)
if err != nil { if err != nil {
@ -94,7 +94,7 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
} }
origAuth := strings.Split(arr[0], " = ") origAuth := strings.Split(arr[0], " = ")
origEmail := strings.Split(arr[1], " = ") origEmail := strings.Split(arr[1], " = ")
authConfig, err := DecodeAuth(origAuth[1]) authConfig, err := decodeAuth(origAuth[1])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -104,13 +104,13 @@ func LoadConfig(rootPath string) (*AuthConfig, error) {
} }
// save the auth config // save the auth config
func SaveConfig(rootPath, authStr string, email string) error { func SaveConfig(authConfig *AuthConfig) error {
confFile := path.Join(rootPath, CONFIGFILE) confFile := path.Join(authConfig.rootPath, CONFIGFILE)
if len(email) == 0 { if len(authConfig.Email) == 0 {
os.Remove(confFile) os.Remove(confFile)
return nil return nil
} }
lines := "auth = " + authStr + "\n" + "email = " + email + "\n" lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n"
b := []byte(lines) b := []byte(lines)
err := ioutil.WriteFile(confFile, b, 0600) err := ioutil.WriteFile(confFile, b, 0600)
if err != nil { if err != nil {
@ -120,7 +120,7 @@ func SaveConfig(rootPath, authStr string, email string) error {
} }
// try to register/login to the registry server // try to register/login to the registry server
func Login(authConfig *AuthConfig) (string, error) { func Login(authConfig *AuthConfig, store bool) (string, error) {
storeConfig := false storeConfig := false
client := &http.Client{} client := &http.Client{}
reqStatusCode := 0 reqStatusCode := 0
@ -168,9 +168,11 @@ func Login(authConfig *AuthConfig) (string, error) {
status = "Login Succeeded\n" status = "Login Succeeded\n"
storeConfig = true storeConfig = true
} else if resp.StatusCode == 401 { } else if resp.StatusCode == 401 {
if err := SaveConfig(authConfig.rootPath, "", ""); err != nil { if store {
if err := SaveConfig(authConfig); err != nil {
return "", err return "", err
} }
}
return "", fmt.Errorf("Wrong login/password, please try again") return "", fmt.Errorf("Wrong login/password, please try again")
} else { } else {
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
@ -182,9 +184,8 @@ func Login(authConfig *AuthConfig) (string, error) {
} else { } else {
return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
} }
if storeConfig { if storeConfig && store {
authStr := EncodeAuth(authConfig) if err := SaveConfig(authConfig); err != nil {
if err := SaveConfig(authConfig.rootPath, authStr, authConfig.Email); err != nil {
return "", err return "", err
} }
} }

View File

@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
remote = name remote = name
} }
if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false)); err != nil { if err := b.srv.ImagePull(remote, tag, "", b.out, utils.NewStreamFormatter(false), nil); err != nil {
return err return err
} }

View File

@ -287,27 +287,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return nil return nil
} }
body, _, err := cli.call("GET", "/auth", nil)
if err != nil {
return err
}
var out auth.AuthConfig
err = json.Unmarshal(body, &out)
if err != nil {
return err
}
var username string var username string
var password string var password string
var email string var email string
fmt.Print("Username (", out.Username, "): ") fmt.Print("Username (", cli.authConfig.Username, "): ")
username = readAndEchoString(os.Stdin, os.Stdout) username = readAndEchoString(os.Stdin, os.Stdout)
if username == "" { if username == "" {
username = out.Username username = cli.authConfig.Username
} }
if username != out.Username { if username != cli.authConfig.Username {
fmt.Print("Password: ") fmt.Print("Password: ")
password = readString(os.Stdin, os.Stdout) password = readString(os.Stdin, os.Stdout)
@ -315,20 +304,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return fmt.Errorf("Error : Password Required") return fmt.Errorf("Error : Password Required")
} }
fmt.Print("Email (", out.Email, "): ") fmt.Print("Email (", cli.authConfig.Email, "): ")
email = readAndEchoString(os.Stdin, os.Stdout) email = readAndEchoString(os.Stdin, os.Stdout)
if email == "" { if email == "" {
email = out.Email email = cli.authConfig.Email
} }
} else { } else {
email = out.Email email = cli.authConfig.Email
} }
term.RestoreTerminal(oldState)
out.Username = username cli.authConfig.Username = username
out.Password = password cli.authConfig.Password = password
out.Email = email cli.authConfig.Email = email
body, _, err = cli.call("POST", "/auth", out) body, _, err := cli.call("POST", "/auth", cli.authConfig)
if err != nil { if err != nil {
return err return err
} }
@ -336,10 +326,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
var out2 APIAuth var out2 APIAuth
err = json.Unmarshal(body, &out2) err = json.Unmarshal(body, &out2)
if err != nil { if err != nil {
auth.LoadConfig(os.Getenv("HOME"))
return err return err
} }
auth.SaveConfig(cli.authConfig)
if out2.Status != "" { if out2.Status != "" {
term.RestoreTerminal(oldState)
fmt.Print(out2.Status) fmt.Print(out2.Status)
} }
return nil return nil
@ -724,18 +715,22 @@ func (cli *DockerCli) CmdPush(args ...string) error {
return nil return nil
} }
username, err := cli.checkIfLogged(*registry == "", "push") if err := cli.checkIfLogged(*registry == "", "push"); err != nil {
if err != nil {
return err return err
} }
if len(strings.SplitN(name, "/", 2)) == 1 { if len(strings.SplitN(name, "/", 2)) == 1 {
return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", username, name) return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in <user>/<repo> (ex: %s/%s)", cli.authConfig.Username, name)
}
buf, err := json.Marshal(cli.authConfig)
if err != nil {
return err
} }
v := url.Values{} v := url.Values{}
v.Set("registry", *registry) v.Set("registry", *registry)
if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, os.Stdout); err != nil { if err := cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), os.Stdout); err != nil {
return err return err
} }
return nil return nil
@ -1296,38 +1291,17 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return nil return nil
} }
func (cli *DockerCli) checkIfLogged(condition bool, action string) (string, error) { func (cli *DockerCli) checkIfLogged(condition bool, action string) error {
body, _, err := cli.call("GET", "/auth", nil)
if err != nil {
return "", err
}
var out auth.AuthConfig
err = json.Unmarshal(body, &out)
if err != nil {
return "", err
}
// If condition AND the login failed // If condition AND the login failed
if condition && out.Username == "" { if condition && cli.authConfig.Username == "" {
if err := cli.CmdLogin(""); err != nil { if err := cli.CmdLogin(""); err != nil {
return "", err return err
} }
if cli.authConfig.Username == "" {
body, _, err = cli.call("GET", "/auth", nil) return fmt.Errorf("Please login prior to %s. ('docker login')", action)
if err != nil {
return "", err
}
err = json.Unmarshal(body, &out)
if err != nil {
return "", err
}
if out.Username == "" {
return "", fmt.Errorf("Please login prior to %s. ('docker login')", action)
} }
} }
return out.Username, nil return nil
} }
func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) { func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, error) {
@ -1514,10 +1488,12 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
} }
func NewDockerCli(addr string, port int) *DockerCli { func NewDockerCli(addr string, port int) *DockerCli {
return &DockerCli{addr, port} authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
return &DockerCli{addr, port, authConfig}
} }
type DockerCli struct { type DockerCli struct {
host string host string
port int port int
authConfig *auth.AuthConfig
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -473,10 +473,7 @@ type Registry struct {
authConfig *auth.AuthConfig authConfig *auth.AuthConfig
} }
func NewRegistry(root string) *Registry { func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
// If the auth file does not exist, keep going
authConfig, _ := auth.LoadConfig(root)
httpTransport := &http.Transport{ httpTransport := &http.Transport{
DisableKeepAlives: true, DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,

View File

@ -68,7 +68,7 @@ func init() {
runtime: runtime, runtime: runtime,
} }
// Retrieve the Image // Retrieve the Image
if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false)); err != nil { if err := srv.ImagePull(unitTestImageName, "", "", os.Stdout, utils.NewStreamFormatter(false), nil); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -55,7 +55,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) { func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
results, err := registry.NewRegistry(srv.runtime.root).SearchRepositories(term) results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -395,8 +395,8 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
return nil return nil
} }
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter) error { func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r := registry.NewRegistry(srv.runtime.root) r := registry.NewRegistry(srv.runtime.root, authConfig)
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
if endpoint != "" { if endpoint != "" {
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil { if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
@ -587,10 +587,10 @@ func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgId,
return nil return nil
} }
func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter) error { func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
out = utils.NewWriteFlusher(out) out = utils.NewWriteFlusher(out)
img, err := srv.runtime.graph.Get(name) img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root) r := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil { if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name]))) out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))