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

65
api.go
View file

@ -13,7 +13,7 @@ import (
"strings"
)
const APIVERSION = 1.1
const APIVERSION = 1.2
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
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 {
// FIXME: Handle multiple login at once
// FIXME: return specific error code if config file missing?
if version > 1.1 {
w.WriteHeader(http.StatusNotFound)
return nil
}
authConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
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 {
// FIXME: Handle multiple login at once
config := &auth.AuthConfig{}
if err := json.NewDecoder(r.Body).Decode(config); err != nil {
authConfig := &auth.AuthConfig{}
err := json.NewDecoder(r.Body).Decode(authConfig)
if err != nil {
return err
}
authConfig, err := auth.LoadConfig(srv.runtime.root)
if err != nil {
if err != auth.ErrConfigFileMissing {
status := ""
if version > 1.1 {
status, err = auth.Login(authConfig, false)
if err != nil {
return err
}
authConfig = &auth.AuthConfig{}
}
if config.Username == authConfig.Username {
config.Password = authConfig.Password
}
} 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(config.Username, config.Password, config.Email, srv.runtime.root)
status, err := auth.Login(newAuthConfig)
if err != nil {
return err
newAuthConfig := auth.NewAuthConfig(authConfig.Username, authConfig.Password, authConfig.Email, srv.runtime.root)
status, err = auth.Login(newAuthConfig, true)
if err != nil {
return err
}
}
if status != "" {
b, err := json.Marshal(&APIAuth{Status: status})
if err != nil {
@ -322,7 +329,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht
sf := utils.NewStreamFormatter(version > 1.0)
if image != "" { //pull
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() {
w.Write(sf.FormatError(err))
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 {
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 {
return err
}
@ -403,7 +422,7 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http
w.Header().Set("Content-Type", "application/json")
}
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() {
w.Write(sf.FormatError(err))
return nil
@ -490,7 +509,7 @@ func deleteImages(srv *Server, version float64, w http.ResponseWriter, r *http.R
return fmt.Errorf("Missing parameter")
}
name := vars["name"]
imgs, err := srv.ImageDelete(name, version > 1.0)
imgs, err := srv.ImageDelete(name, version > 1.1)
if err != nil {
return err
}

View file

@ -6,7 +6,6 @@ import (
"bytes"
"encoding/json"
"github.com/dotcloud/docker/auth"
"github.com/dotcloud/docker/registry"
"github.com/dotcloud/docker/utils"
"io"
"net"
@ -18,7 +17,7 @@ import (
"time"
)
func TestGetAuth(t *testing.T) {
func TestPostAuth(t *testing.T) {
runtime, err := newTestRuntime()
if err != nil {
t.Fatal(err)
@ -54,12 +53,6 @@ func TestGetAuth(t *testing.T) {
if r.Code != http.StatusOK && r.Code != 0 {
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) {
@ -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) {
runtime, err := newTestRuntime()
if err != nil {

View file

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

View file

@ -61,7 +61,7 @@ func (b *buildFile) CmdFrom(name string) error {
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
}

View file

@ -287,27 +287,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
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 password string
var email string
fmt.Print("Username (", out.Username, "): ")
fmt.Print("Username (", cli.authConfig.Username, "): ")
username = readAndEchoString(os.Stdin, os.Stdout)
if username == "" {
username = out.Username
username = cli.authConfig.Username
}
if username != out.Username {
if username != cli.authConfig.Username {
fmt.Print("Password: ")
password = readString(os.Stdin, os.Stdout)
@ -315,20 +304,21 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
return fmt.Errorf("Error : Password Required")
}
fmt.Print("Email (", out.Email, "): ")
fmt.Print("Email (", cli.authConfig.Email, "): ")
email = readAndEchoString(os.Stdin, os.Stdout)
if email == "" {
email = out.Email
email = cli.authConfig.Email
}
} else {
email = out.Email
email = cli.authConfig.Email
}
term.RestoreTerminal(oldState)
out.Username = username
out.Password = password
out.Email = email
cli.authConfig.Username = username
cli.authConfig.Password = password
cli.authConfig.Email = email
body, _, err = cli.call("POST", "/auth", out)
body, _, err := cli.call("POST", "/auth", cli.authConfig)
if err != nil {
return err
}
@ -336,10 +326,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
var out2 APIAuth
err = json.Unmarshal(body, &out2)
if err != nil {
auth.LoadConfig(os.Getenv("HOME"))
return err
}
auth.SaveConfig(cli.authConfig)
if out2.Status != "" {
term.RestoreTerminal(oldState)
fmt.Print(out2.Status)
}
return nil
@ -724,18 +715,22 @@ func (cli *DockerCli) CmdPush(args ...string) error {
return nil
}
username, err := cli.checkIfLogged(*registry == "", "push")
if err != nil {
if err := cli.checkIfLogged(*registry == "", "push"); err != nil {
return err
}
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.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 nil
@ -1296,38 +1291,17 @@ func (cli *DockerCli) CmdRun(args ...string) error {
return nil
}
func (cli *DockerCli) checkIfLogged(condition bool, action string) (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
}
func (cli *DockerCli) checkIfLogged(condition bool, action string) error {
// If condition AND the login failed
if condition && out.Username == "" {
if condition && cli.authConfig.Username == "" {
if err := cli.CmdLogin(""); err != nil {
return "", err
return err
}
body, _, err = cli.call("GET", "/auth", nil)
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)
if cli.authConfig.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) {
@ -1514,10 +1488,12 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
}
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 {
host string
port int
host string
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
}
func NewRegistry(root string) *Registry {
// If the auth file does not exist, keep going
authConfig, _ := auth.LoadConfig(root)
func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
httpTransport := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,

View file

@ -68,7 +68,7 @@ func init() {
runtime: runtime,
}
// 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)
}
}

View file

@ -55,7 +55,7 @@ func (srv *Server) ContainerExport(name string, out io.Writer) 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 {
return nil, err
}
@ -395,8 +395,8 @@ func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, local, re
return nil
}
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter) error {
r := registry.NewRegistry(srv.runtime.root)
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
r := registry.NewRegistry(srv.runtime.root, authConfig)
out = utils.NewWriteFlusher(out)
if endpoint != "" {
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
}
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)
img, err := srv.runtime.graph.Get(name)
r := registry.NewRegistry(srv.runtime.root)
r := registry.NewRegistry(srv.runtime.root, authConfig)
if err != nil {
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))