diff --git a/api.go b/api.go index b6ab7badfa..975134f22d 100644 --- a/api.go +++ b/api.go @@ -81,54 +81,15 @@ func getBoolParam(value string) (bool, error) { return ret, nil } -func getAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version > 1.1 { - w.WriteHeader(http.StatusNotFound) - return nil - } - authConfig, err := auth.LoadConfig(srv.runtime.root) - if err != nil { - if err != auth.ErrConfigFileMissing { - return err - } - authConfig = &auth.AuthConfig{} - } - b, err := json.Marshal(&auth.AuthConfig{Username: authConfig.Username, Email: authConfig.Email}) - if err != nil { - return err - } - writeJSON(w, b) - return nil -} - func postAuth(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { authConfig := &auth.AuthConfig{} err := json.NewDecoder(r.Body).Decode(authConfig) if err != nil { return err } - status := "" - 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 != auth.ErrConfigFileMissing { - return err - } - } - if authConfig.Username == localAuthConfig.Username { - authConfig.Password = localAuthConfig.Password - } - - newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root) - status, err = auth.Login(newAuthConfig, true) - if err != nil { - return err - } + status, err := auth.Login(authConfig) + if err != nil { + return err } if status != "" { b, err := json.Marshal(&APIAuth{Status: status}) @@ -429,16 +390,8 @@ 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 { 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 := json.NewDecoder(r.Body).Decode(authConfig); err != nil { + return err } if err := parseForm(r); err != nil { return err @@ -854,7 +807,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { m := map[string]map[string]func(*Server, float64, http.ResponseWriter, *http.Request, map[string]string) error{ "GET": { - "/auth": getAuth, "/version": getVersion, "/info": getInfo, "/images/json": getImagesJSON, diff --git a/auth/auth.go b/auth/auth.go index 97df928b6b..8f4d09fb8f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -25,19 +25,15 @@ var ( ) type AuthConfig struct { - Username string `json:"username"` - Password string `json:"password"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` Email string `json:"email"` - rootPath string } -func NewAuthConfig(username, password, email, rootPath string) *AuthConfig { - return &AuthConfig{ - Username: username, - Password: password, - Email: email, - rootPath: rootPath, - } +type ConfigFile struct { + Configs map[string]AuthConfig `json:"configs,omitempty"` + rootPath string } func IndexServerAddress() string { @@ -54,61 +50,83 @@ func encodeAuth(authConfig *AuthConfig) string { } // decode the auth string -func decodeAuth(authStr string) (*AuthConfig, error) { +func decodeAuth(authStr string) (string, string, error) { decLen := base64.StdEncoding.DecodedLen(len(authStr)) decoded := make([]byte, decLen) authByte := []byte(authStr) n, err := base64.StdEncoding.Decode(decoded, authByte) if err != nil { - return nil, err + return "", "", err } if n > decLen { - return nil, fmt.Errorf("Something went wrong decoding auth config") + return "", "", fmt.Errorf("Something went wrong decoding auth config") } arr := strings.Split(string(decoded), ":") if len(arr) != 2 { - return nil, fmt.Errorf("Invalid auth configuration file") + return "", "", fmt.Errorf("Invalid auth configuration file") } password := strings.Trim(arr[1], "\x00") - return &AuthConfig{Username: arr[0], Password: password}, nil + return arr[0], password, nil } // load up the auth config information and return values // FIXME: use the internal golang config parser -func LoadConfig(rootPath string) (*AuthConfig, error) { +func LoadConfig(rootPath string) (*ConfigFile, error) { + configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} confFile := path.Join(rootPath, CONFIGFILE) if _, err := os.Stat(confFile); err != nil { - return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing + return &configFile, ErrConfigFileMissing } b, err := ioutil.ReadFile(confFile) if err != nil { return nil, err } - arr := strings.Split(string(b), "\n") - if len(arr) < 2 { - return nil, fmt.Errorf("The Auth config file is empty") + + if err := json.Unmarshal(b, &configFile.Configs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return nil, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) + if err != nil { + return nil, err + } + origEmail := strings.Split(arr[1], " = ") + authConfig.Email = origEmail[1] + configFile.Configs[IndexServerAddress()] = authConfig + } else { + for k, authConfig := range configFile.Configs { + authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) + if err != nil { + return nil, err + } + configFile.Configs[k] = authConfig + } } - origAuth := strings.Split(arr[0], " = ") - origEmail := strings.Split(arr[1], " = ") - authConfig, err := decodeAuth(origAuth[1]) - if err != nil { - return nil, err - } - authConfig.Email = origEmail[1] - authConfig.rootPath = rootPath - return authConfig, nil + return &configFile, nil } // save the auth config -func SaveConfig(authConfig *AuthConfig) error { - confFile := path.Join(authConfig.rootPath, CONFIGFILE) - if len(authConfig.Email) == 0 { +func SaveConfig(configFile *ConfigFile) error { + confFile := path.Join(configFile.rootPath, CONFIGFILE) + if len(configFile.Configs) == 0 { os.Remove(confFile) return nil } - lines := "auth = " + encodeAuth(authConfig) + "\n" + "email = " + authConfig.Email + "\n" - b := []byte(lines) - err := ioutil.WriteFile(confFile, b, 0600) + for k, authConfig := range configFile.Configs { + authConfig.Auth = encodeAuth(&authConfig) + authConfig.Username = "" + authConfig.Password = "" + configFile.Configs[k] = authConfig + } + + b, err := json.Marshal(configFile.Configs) + if err != nil { + return err + } + err = ioutil.WriteFile(confFile, b, 0600) if err != nil { return err } @@ -116,8 +134,7 @@ func SaveConfig(authConfig *AuthConfig) error { } // try to register/login to the registry server -func Login(authConfig *AuthConfig, store bool) (string, error) { - storeConfig := false +func Login(authConfig *AuthConfig) (string, error) { client := &http.Client{} reqStatusCode := 0 var status string @@ -143,7 +160,6 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { if reqStatusCode == 201 { status = "Account created. Please use the confirmation link we sent" + " to your e-mail to activate it." - storeConfig = true } else if reqStatusCode == 403 { return "", fmt.Errorf("Login: Your account hasn't been activated. " + "Please check your e-mail for a confirmation link.") @@ -162,14 +178,7 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { } if resp.StatusCode == 200 { status = "Login Succeeded" - storeConfig = true } else if resp.StatusCode == 401 { - if store { - authConfig.Email = "" - if err := SaveConfig(authConfig); err != nil { - return "", err - } - } return "", fmt.Errorf("Wrong login/password, please try again") } else { return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, @@ -181,10 +190,5 @@ func Login(authConfig *AuthConfig, store bool) (string, error) { } else { return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody) } - if storeConfig && store { - if err := SaveConfig(authConfig); err != nil { - return "", err - } - } return status, nil } diff --git a/commands.go b/commands.go index b0e32162e6..17597320b5 100644 --- a/commands.go +++ b/commands.go @@ -313,16 +313,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error { email string ) + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] + if !ok { + authconfig = auth.AuthConfig{} + } + if *flUsername == "" { - fmt.Fprintf(cli.out, "Username (%s): ", cli.authConfig.Username) + fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) if username == "" { - username = cli.authConfig.Username + username = authconfig.Username } } else { username = *flUsername } - if username != cli.authConfig.Username { + if username != authconfig.Username { if *flPassword == "" { fmt.Fprintf(cli.out, "Password: ") password = readString(cli.in, cli.out) @@ -334,31 +339,30 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if *flEmail == "" { - fmt.Fprintf(cli.out, "Email (%s): ", cli.authConfig.Email) + fmt.Fprintf(cli.out, "Email (%s): ", authconfig.Email) email = readAndEchoString(cli.in, cli.out) if email == "" { - email = cli.authConfig.Email + email = authconfig.Email } } else { email = *flEmail } } else { - password = cli.authConfig.Password - email = cli.authConfig.Email + password = authconfig.Password + email = authconfig.Email } if oldState != nil { term.RestoreTerminal(cli.terminalFd, oldState) } - cli.authConfig.Username = username - cli.authConfig.Password = password - cli.authConfig.Email = email + authconfig.Username = username + authconfig.Password = password + authconfig.Email = email + cli.configFile.Configs[auth.IndexServerAddress()] = authconfig - body, statusCode, err := cli.call("POST", "/auth", cli.authConfig) + body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()]) if statusCode == 401 { - cli.authConfig.Username = "" - cli.authConfig.Password = "" - cli.authConfig.Email = "" - auth.SaveConfig(cli.authConfig) + delete(cli.configFile.Configs, auth.IndexServerAddress()) + auth.SaveConfig(cli.configFile) return err } if err != nil { @@ -368,10 +372,10 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var out2 APIAuth err = json.Unmarshal(body, &out2) if err != nil { - auth.LoadConfig(os.Getenv("HOME")) + cli.configFile, _ = auth.LoadConfig(os.Getenv("HOME")) return err } - auth.SaveConfig(cli.authConfig) + auth.SaveConfig(cli.configFile) if out2.Status != "" { fmt.Fprintf(cli.out, "%s\n", out2.Status) } @@ -802,10 +806,10 @@ func (cli *DockerCli) CmdPush(args ...string) error { // Custom repositories can have different rules, and we must also // allow pushing by image ID. if len(strings.SplitN(name, "/", 2)) == 1 { - return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.authConfig.Username, name) + return fmt.Errorf("Impossible to push a \"root\" repository. Please rename your repository in / (ex: %s/%s)", cli.configFile.Configs[auth.IndexServerAddress()].Username, name) } - buf, err := json.Marshal(cli.authConfig) + buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) if err != nil { return err } @@ -1410,11 +1414,11 @@ func (cli *DockerCli) CmdRun(args ...string) error { func (cli *DockerCli) checkIfLogged(action string) error { // If condition AND the login failed - if cli.authConfig.Username == "" { + if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { if err := cli.CmdLogin(""); err != nil { return err } - if cli.authConfig.Username == "" { + if cli.configFile.Configs[auth.IndexServerAddress()].Username == "" { return fmt.Errorf("Please login prior to %s. ('docker login')", action) } } @@ -1670,11 +1674,11 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc err = out } - authConfig, _ := auth.LoadConfig(os.Getenv("HOME")) + configFile, _ := auth.LoadConfig(os.Getenv("HOME")) return &DockerCli{ proto: proto, addr: addr, - authConfig: authConfig, + configFile: configFile, in: in, out: out, err: err, @@ -1686,7 +1690,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *Doc type DockerCli struct { proto string addr string - authConfig *auth.AuthConfig + configFile *auth.ConfigFile in io.ReadCloser out io.Writer err io.Writer