Remove email address field from login

This removes the email prompt when you use docker login, and also removes the ability to register via the docker cli. Docker login, will strictly be used for logging into a registry server.

Signed-off-by: Ken Cochrane <kencochrane@gmail.com>
This commit is contained in:
Ken Cochrane 2016-02-29 17:51:36 -08:00 committed by Derek McGowan
parent 6a96006959
commit aee260d4eb
18 changed files with 180 additions and 182 deletions

View File

@ -17,7 +17,7 @@ import (
"github.com/docker/engine-api/types" "github.com/docker/engine-api/types"
) )
// CmdLogin logs in or registers a user to a Docker registry service. // CmdLogin logs in a user to a Docker registry service.
// //
// If no server is specified, the user will be logged into or registered to the registry's index server. // If no server is specified, the user will be logged into or registered to the registry's index server.
// //
@ -28,7 +28,9 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
flUser := cmd.String([]string{"u", "-username"}, "", "Username") flUser := cmd.String([]string{"u", "-username"}, "", "Username")
flPassword := cmd.String([]string{"p", "-password"}, "", "Password") flPassword := cmd.String([]string{"p", "-password"}, "", "Password")
flEmail := cmd.String([]string{"e", "-email"}, "", "Email")
// Deprecated in 1.11: Should be removed in docker 1.13
cmd.String([]string{"#e", "#-email"}, "", "Email")
cmd.ParseFlags(args, true) cmd.ParseFlags(args, true)
@ -38,13 +40,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
} }
var serverAddress string var serverAddress string
var isDefaultRegistry bool
if len(cmd.Args()) > 0 { if len(cmd.Args()) > 0 {
serverAddress = cmd.Arg(0) serverAddress = cmd.Arg(0)
} else { } else {
serverAddress = cli.electAuthServer() serverAddress = cli.electAuthServer()
isDefaultRegistry = true
} }
authConfig, err := cli.configureAuth(*flUser, *flPassword, *flEmail, serverAddress) authConfig, err := cli.configureAuth(*flUser, *flPassword, serverAddress, isDefaultRegistry)
if err != nil { if err != nil {
return err return err
} }
@ -77,7 +81,7 @@ func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
} }
} }
func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress string) (types.AuthConfig, error) { func (cli *DockerCli) configureAuth(flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
authconfig, err := getCredentials(cli.configFile, serverAddress) authconfig, err := getCredentials(cli.configFile, serverAddress)
if err != nil { if err != nil {
return authconfig, err return authconfig, err
@ -86,6 +90,10 @@ func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress s
authconfig.Username = strings.TrimSpace(authconfig.Username) authconfig.Username = strings.TrimSpace(authconfig.Username)
if flUser = strings.TrimSpace(flUser); flUser == "" { if flUser = strings.TrimSpace(flUser); flUser == "" {
if isDefaultRegistry {
// if this is a defauly registry (docker hub), then display the following message.
fmt.Fprintln(cli.out, "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
}
cli.promptWithDefault("Username", authconfig.Username) cli.promptWithDefault("Username", authconfig.Username)
flUser = readInput(cli.in, cli.out) flUser = readInput(cli.in, cli.out)
flUser = strings.TrimSpace(flUser) flUser = strings.TrimSpace(flUser)
@ -115,29 +123,8 @@ func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress s
} }
} }
// Assume that a different username means they may not want to use
// the email from the config file, so prompt it
if flUser != authconfig.Username {
if flEmail == "" {
cli.promptWithDefault("Email", authconfig.Email)
flEmail = readInput(cli.in, cli.out)
if flEmail == "" {
flEmail = authconfig.Email
}
}
} else {
// However, if they don't override the username use the
// email from the cmd line if specified. IOW, allow
// then to change/override them. And if not specified, just
// use what's in the config file
if flEmail == "" {
flEmail = authconfig.Email
}
}
authconfig.Username = flUser authconfig.Username = flUser
authconfig.Password = flPassword authconfig.Password = flPassword
authconfig.Email = flEmail
authconfig.ServerAddress = serverAddress authconfig.ServerAddress = serverAddress
return authconfig, nil return authconfig, nil

View File

@ -48,7 +48,7 @@ func (cli *DockerCli) registryAuthenticationPrivilegedFunc(index *registrytypes.
return func() (string, error) { return func() (string, error) {
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
indexServer := registry.GetAuthConfigKey(index) indexServer := registry.GetAuthConfigKey(index)
authConfig, err := cli.configureAuth("", "", "", indexServer) authConfig, err := cli.configureAuth("", "", indexServer, false)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -88,11 +88,6 @@ func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
if err != nil { if err != nil {
return err return err
} }
origEmail := strings.Split(arr[1], " = ")
if len(origEmail) != 2 {
return fmt.Errorf("Invalid Auth config file")
}
authConfig.Email = origEmail[1]
authConfig.ServerAddress = defaultIndexserver authConfig.ServerAddress = defaultIndexserver
configFile.AuthConfigs[defaultIndexserver] = authConfig configFile.AuthConfigs[defaultIndexserver] = authConfig
} else { } else {

View File

@ -111,12 +111,9 @@ func TestOldInvalidsAuth(t *testing.T) {
invalids := map[string]string{ invalids := map[string]string{
`username = test`: "The Auth config file is empty", `username = test`: "The Auth config file is empty",
`username `username
password password`: "Invalid Auth config file",
email`: "Invalid Auth config file",
`username = test `username = test
email`: "Invalid auth configuration file", email`: "Invalid auth configuration file",
`username = am9lam9lOmhlbGxv
email`: "Invalid Auth config file",
} }
tmpHome, err := ioutil.TempDir("", "config-test") tmpHome, err := ioutil.TempDir("", "config-test")
@ -164,7 +161,7 @@ func TestOldValidAuth(t *testing.T) {
fn := filepath.Join(tmpHome, oldConfigfile) fn := filepath.Join(tmpHome, oldConfigfile)
js := `username = am9lam9lOmhlbGxv js := `username = am9lam9lOmhlbGxv
email = user@example.com` email = user@example.com`
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -176,15 +173,23 @@ email = user@example.com`
// defaultIndexserver is https://index.docker.io/v1/ // defaultIndexserver is https://index.docker.io/v1/
ac := config.AuthConfigs["https://index.docker.io/v1/"] ac := config.AuthConfigs["https://index.docker.io/v1/"]
if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { if ac.Username != "joejoe" || ac.Password != "hello" {
t.Fatalf("Missing data from parsing:\n%q", config) t.Fatalf("Missing data from parsing:\n%q", config)
} }
// Now save it and make sure it shows up in new form // Now save it and make sure it shows up in new form
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
if !strings.Contains(configStr, "user@example.com") { expConfStr := `{
t.Fatalf("Should have save in new form: %s", configStr) "auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv"
}
}
}`
if configStr != expConfStr {
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
} }
} }
@ -239,15 +244,24 @@ func TestOldJson(t *testing.T) {
} }
ac := config.AuthConfigs["https://index.docker.io/v1/"] ac := config.AuthConfigs["https://index.docker.io/v1/"]
if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { if ac.Username != "joejoe" || ac.Password != "hello" {
t.Fatalf("Missing data from parsing:\n%q", config) t.Fatalf("Missing data from parsing:\n%q", config)
} }
// Now save it and make sure it shows up in new form // Now save it and make sure it shows up in new form
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
if !strings.Contains(configStr, "user@example.com") { expConfStr := `{
t.Fatalf("Should have save in new form: %s", configStr) "auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv",
"email": "user@example.com"
}
}
}`
if configStr != expConfStr {
t.Fatalf("Should have save in new form: \n'%s'\n not \n'%s'\n", configStr, expConfStr)
} }
} }
@ -259,7 +273,7 @@ func TestNewJson(t *testing.T) {
defer os.RemoveAll(tmpHome) defer os.RemoveAll(tmpHome)
fn := filepath.Join(tmpHome, ConfigFileName) fn := filepath.Join(tmpHome, ConfigFileName)
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil { if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -270,15 +284,62 @@ func TestNewJson(t *testing.T) {
} }
ac := config.AuthConfigs["https://index.docker.io/v1/"] ac := config.AuthConfigs["https://index.docker.io/v1/"]
if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { if ac.Username != "joejoe" || ac.Password != "hello" {
t.Fatalf("Missing data from parsing:\n%q", config) t.Fatalf("Missing data from parsing:\n%q", config)
} }
// Now save it and make sure it shows up in new form // Now save it and make sure it shows up in new form
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome) configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
if !strings.Contains(configStr, "user@example.com") { expConfStr := `{
t.Fatalf("Should have save in new form: %s", configStr) "auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv"
}
}
}`
if configStr != expConfStr {
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
}
}
func TestNewJsonNoEmail(t *testing.T) {
tmpHome, err := ioutil.TempDir("", "config-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpHome)
fn := filepath.Join(tmpHome, ConfigFileName)
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } } }`
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
t.Fatal(err)
}
config, err := Load(tmpHome)
if err != nil {
t.Fatalf("Failed loading on empty json file: %q", err)
}
ac := config.AuthConfigs["https://index.docker.io/v1/"]
if ac.Username != "joejoe" || ac.Password != "hello" {
t.Fatalf("Missing data from parsing:\n%q", config)
}
// Now save it and make sure it shows up in new form
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
expConfStr := `{
"auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv"
}
}
}`
if configStr != expConfStr {
t.Fatalf("Should have save in new form: \n%s\n not \n%s", configStr, expConfStr)
} }
} }
@ -366,7 +427,7 @@ func TestJsonReaderNoFile(t *testing.T) {
} }
ac := config.AuthConfigs["https://index.docker.io/v1/"] ac := config.AuthConfigs["https://index.docker.io/v1/"]
if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { if ac.Username != "joejoe" || ac.Password != "hello" {
t.Fatalf("Missing data from parsing:\n%q", config) t.Fatalf("Missing data from parsing:\n%q", config)
} }
@ -381,7 +442,7 @@ func TestOldJsonReaderNoFile(t *testing.T) {
} }
ac := config.AuthConfigs["https://index.docker.io/v1/"] ac := config.AuthConfigs["https://index.docker.io/v1/"]
if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { if ac.Username != "joejoe" || ac.Password != "hello" {
t.Fatalf("Missing data from parsing:\n%q", config) t.Fatalf("Missing data from parsing:\n%q", config)
} }
} }
@ -404,7 +465,7 @@ func TestJsonWithPsFormatNoFile(t *testing.T) {
func TestJsonSaveWithNoFile(t *testing.T) { func TestJsonSaveWithNoFile(t *testing.T) {
js := `{ js := `{
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } }, "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}" "psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
}` }`
config, err := LoadFromReader(strings.NewReader(js)) config, err := LoadFromReader(strings.NewReader(js))
@ -426,9 +487,16 @@ func TestJsonSaveWithNoFile(t *testing.T) {
t.Fatalf("Failed saving to file: %q", err) t.Fatalf("Failed saving to file: %q", err)
} }
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
if !strings.Contains(string(buf), `"auths":`) || expConfStr := `{
!strings.Contains(string(buf), "user@example.com") { "auths": {
t.Fatalf("Should have save in new form: %s", string(buf)) "https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv"
}
},
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
}`
if string(buf) != expConfStr {
t.Fatalf("Should have save in new form: \n%s\nnot \n%s", string(buf), expConfStr)
} }
} }
@ -454,14 +522,23 @@ func TestLegacyJsonSaveWithNoFile(t *testing.T) {
t.Fatalf("Failed saving to file: %q", err) t.Fatalf("Failed saving to file: %q", err)
} }
buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName)) buf, err := ioutil.ReadFile(filepath.Join(tmpHome, ConfigFileName))
if !strings.Contains(string(buf), `"auths":`) ||
!strings.Contains(string(buf), "user@example.com") { expConfStr := `{
t.Fatalf("Should have save in new form: %s", string(buf)) "auths": {
"https://index.docker.io/v1/": {
"auth": "am9lam9lOmhlbGxv",
"email": "user@example.com"
}
}
}`
if string(buf) != expConfStr {
t.Fatalf("Should have save in new form: \n%s\n not \n%s", string(buf), expConfStr)
} }
} }
func TestEncodeAuth(t *testing.T) { func TestEncodeAuth(t *testing.T) {
newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} newAuthConfig := &types.AuthConfig{Username: "ken", Password: "test"}
authStr := encodeAuth(newAuthConfig) authStr := encodeAuth(newAuthConfig)
decAuthConfig := &types.AuthConfig{} decAuthConfig := &types.AuthConfig{}
var err error var err error

View File

@ -811,7 +811,7 @@ _docker_daemon() {
return return
;; ;;
esac esac
local key=$(__docker_map_key_of_current_option '--storage-opt') local key=$(__docker_map_key_of_current_option '--storage-opt')
case "$key" in case "$key" in
dm.@(blkdiscard|override_udev_sync_check|use_deferred_@(removal|deletion))) dm.@(blkdiscard|override_udev_sync_check|use_deferred_@(removal|deletion)))
@ -1205,14 +1205,14 @@ _docker_load() {
_docker_login() { _docker_login() {
case "$prev" in case "$prev" in
--email|-e|--password|-p|--username|-u) --password|-p|--username|-u)
return return
;; ;;
esac esac
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=( $( compgen -W "--email -e --help --password -p --username -u" -- "$cur" ) ) COMPREPLY=( $( compgen -W "--help --password -p --username -u" -- "$cur" ) )
;; ;;
esac esac
} }

View File

@ -221,8 +221,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from load' -l help -d 'Print
complete -c docker -A -f -n '__fish_seen_subcommand_from load' -s i -l input -d 'Read from a tar archive file, instead of STDIN' complete -c docker -A -f -n '__fish_seen_subcommand_from load' -s i -l input -d 'Read from a tar archive file, instead of STDIN'
# login # login
complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Register or log in to a Docker registry server' complete -c docker -f -n '__fish_docker_no_subcommand' -a login -d 'Log in to a Docker registry server'
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s e -l email -d 'Email'
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -l help -d 'Print usage' complete -c docker -A -f -n '__fish_seen_subcommand_from login' -l help -d 'Print usage'
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'Password' complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s p -l password -d 'Password'
complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'Username' complete -c docker -A -f -n '__fish_seen_subcommand_from login' -s u -l username -d 'Username'
@ -399,5 +398,3 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -a version -d 'Show the D
complete -c docker -f -n '__fish_docker_no_subcommand' -a wait -d 'Block until a container stops, then print its exit code' complete -c docker -f -n '__fish_docker_no_subcommand' -a wait -d 'Block until a container stops, then print its exit code'
complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -l help -d 'Print usage' complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -l help -d 'Print usage'
complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -a '(__fish_print_docker_containers running)' -d "Container" complete -c docker -A -f -n '__fish_seen_subcommand_from wait' -a '(__fish_print_docker_containers running)' -d "Container"

View File

@ -812,7 +812,6 @@ __docker_subcommand() {
(login) (login)
_arguments $(__docker_arguments) \ _arguments $(__docker_arguments) \
$opts_help \ $opts_help \
"($help -e --email)"{-e=,--email=}"[Email]:email: " \
"($help -p --password)"{-p=,--password=}"[Password]:password: " \ "($help -p --password)"{-p=,--password=}"[Password]:password: " \
"($help -u --user)"{-u=,--user=}"[Username]:username: " \ "($help -u --user)"{-u=,--user=}"[Username]:username: " \
"($help -)1:server: " && ret=0 "($help -)1:server: " && ret=0

View File

@ -14,6 +14,13 @@ weight=80
The following list of features are deprecated in Engine. The following list of features are deprecated in Engine.
### `-e` and `--email` flags on `docker login`
**Deprecated In Release: v1.11**
**Target For Removal In Release: v1.13**
The docker login command is removing the ability to automatically register for an account with the target registry if the given username doesn't exist. Due to this change, the email flag is no longer required, and will be deprecated.
### Ambiguous event fields in API ### Ambiguous event fields in API
**Deprecated In Release: v1.10** **Deprecated In Release: v1.10**

View File

@ -12,10 +12,9 @@ parent = "smn_cli"
Usage: docker login [OPTIONS] [SERVER] Usage: docker login [OPTIONS] [SERVER]
Register or log in to a Docker registry server, if no server is Log in to a Docker registry server, if no server is
specified "https://index.docker.io/v1/" is the default. specified "https://index.docker.io/v1/" is the default.
-e, --email="" Email
--help Print usage --help Print usage
-p, --password="" Password -p, --password="" Password
-u, --username="" Username -u, --username="" Username
@ -27,10 +26,10 @@ adding the server name.
$ docker login localhost:8080 $ docker login localhost:8080
`docker login` requires user to use `sudo` or be `root`, except when: `docker login` requires user to use `sudo` or be `root`, except when:
1. connecting to a remote daemon, such as a `docker-machine` provisioned `docker engine`. 1. connecting to a remote daemon, such as a `docker-machine` provisioned `docker engine`.
2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/security/security/#docker-daemon-attack-surface) for details. 2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/security/security/#docker-daemon-attack-surface) for details.
You can log into any public or private repository for which you have You can log into any public or private repository for which you have
credentials. When you log in, the command stores encoded credentials in credentials. When you log in, the command stores encoded credentials in

View File

@ -33,15 +33,11 @@ Docker itself provides access to Docker Hub services via the `docker search`,
### Account creation and login ### Account creation and login
Typically, you'll want to start by creating an account on Docker Hub (if you haven't Typically, you'll want to start by creating an account on Docker Hub (if you haven't
already) and logging in. You can create your account directly on already) and logging in. You can create your account directly on
[Docker Hub](https://hub.docker.com/account/signup/), or by running: [Docker Hub](https://hub.docker.com/account/signup/).
$ docker login $ docker login
This will prompt you for a user name, which will become the public namespace for your You can now commit and push your own images up to your repos on Docker Hub.
public repositories.
If your user name is available, Docker will prompt you to enter a password and your
e-mail address. It will then automatically log you in. You can now commit and
push your own images up to your repos on Docker Hub.
> **Note:** > **Note:**
> Your authentication credentials will be stored in the `~/.docker/config.json` > Your authentication credentials will be stored in the `~/.docker/config.json`

View File

@ -6548,7 +6548,7 @@ func (s *DockerSuite) TestBuildWorkdirWindowsPath(c *check.C) {
} }
func (s *DockerRegistryAuthSuite) TestBuildFromAuthenticatedRegistry(c *check.C) { func (s *DockerRegistryAuthSuite) TestBuildFromAuthenticatedRegistry(c *check.C) {
dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, "-e", s.reg.email, privateRegistryURL) dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
baseImage := privateRegistryURL + "/baseimage" baseImage := privateRegistryURL + "/baseimage"

View File

@ -20,11 +20,25 @@ func (s *DockerSuite) TestLoginWithoutTTY(c *check.C) {
} }
func (s *DockerRegistryAuthSuite) TestLoginToPrivateRegistry(c *check.C) { func (s *DockerRegistryAuthSuite) TestLoginToPrivateRegistry(c *check.C) {
// wrong credentials
out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", privateRegistryURL)
c.Assert(err, checker.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "401 Unauthorized")
// now it's fine
dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, privateRegistryURL)
}
func (s *DockerRegistryAuthSuite) TestLoginToPrivateRegistryDeprecatedEmailFlag(c *check.C) {
// Test to make sure login still works with the deprecated -e and --email flags
// wrong credentials // wrong credentials
out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", "-e", s.reg.email, privateRegistryURL) out, _, err := dockerCmdWithError("login", "-u", s.reg.username, "-p", "WRONGPASSWORD", "-e", s.reg.email, privateRegistryURL)
c.Assert(err, checker.NotNil, check.Commentf(out)) c.Assert(err, checker.NotNil, check.Commentf(out))
c.Assert(out, checker.Contains, "401 Unauthorized") c.Assert(out, checker.Contains, "401 Unauthorized")
// now it's fine // now it's fine
// -e flag
dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, "-e", s.reg.email, privateRegistryURL) dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, "-e", s.reg.email, privateRegistryURL)
// --email flag
dockerCmd(c, "login", "-u", s.reg.username, "-p", s.reg.password, "--email", s.reg.email, privateRegistryURL)
} }

View File

@ -390,7 +390,6 @@ func (s *DockerRegistryAuthSuite) TestPullWithExternalAuth(c *check.C) {
b, err := ioutil.ReadFile(configPath) b, err := ioutil.ReadFile(configPath)
c.Assert(err, checker.IsNil) c.Assert(err, checker.IsNil)
c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":") c.Assert(string(b), checker.Not(checker.Contains), "\"auth\":")
c.Assert(string(b), checker.Contains, "email")
dockerCmd(c, "--config", tmp, "tag", "busybox", repoName) dockerCmd(c, "--config", tmp, "tag", "busybox", repoName)
dockerCmd(c, "--config", tmp, "push", repoName) dockerCmd(c, "--config", tmp, "push", repoName)

View File

@ -112,7 +112,7 @@ func (s *DockerRegistrySuite) TestV1(c *check.C) {
s.d.Cmd("run", repoName) s.d.Cmd("run", repoName)
c.Assert(v1Repo, check.Not(check.Equals), 1, check.Commentf("Expected v1 repository access after run")) c.Assert(v1Repo, check.Not(check.Equals), 1, check.Commentf("Expected v1 repository access after run"))
s.d.Cmd("login", "-u", "richard", "-p", "testtest", "-e", "testuser@testdomain.com", reg.hostport) s.d.Cmd("login", "-u", "richard", "-p", "testtest", reg.hostport)
c.Assert(v1Logins, check.Not(check.Equals), 0, check.Commentf("Expected v1 login attempt")) c.Assert(v1Logins, check.Not(check.Equals), 0, check.Commentf("Expected v1 login attempt"))
s.d.Cmd("tag", "busybox", repoName) s.d.Cmd("tag", "busybox", repoName)

View File

@ -2,26 +2,25 @@
% Docker Community % Docker Community
% JUNE 2014 % JUNE 2014
# NAME # NAME
docker-login - Register or log in to a Docker registry. docker-login - Log in to a Docker registry.
# SYNOPSIS # SYNOPSIS
**docker login** **docker login**
[**-e**|**--email**[=*EMAIL*]]
[**--help**] [**--help**]
[**-p**|**--password**[=*PASSWORD*]] [**-p**|**--password**[=*PASSWORD*]]
[**-u**|**--username**[=*USERNAME*]] [**-u**|**--username**[=*USERNAME*]]
[SERVER] [SERVER]
# DESCRIPTION # DESCRIPTION
Register or log in to a Docker Registry located on the specified Log in to a Docker Registry located on the specified
`SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you `SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you
do not specify a `SERVER`, the command uses Docker's public registry located at do not specify a `SERVER`, the command uses Docker's public registry located at
`https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub. `https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub.
`docker login` requires user to use `sudo` or be `root`, except when: `docker login` requires user to use `sudo` or be `root`, except when:
1. connecting to a remote daemon, such as a `docker-machine` provisioned `docker engine`. 1. connecting to a remote daemon, such as a `docker-machine` provisioned `docker engine`.
2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/articles/security/#docker-daemon-attack-surface) for details. 2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/articles/security/#docker-daemon-attack-surface) for details.
You can log into any public or private repository for which you have You can log into any public or private repository for which you have
credentials. When you log in, the command stores encoded credentials in credentials. When you log in, the command stores encoded credentials in
@ -31,9 +30,6 @@ credentials. When you log in, the command stores encoded credentials in
> >
# OPTIONS # OPTIONS
**-e**, **--email**=""
Email
**--help** **--help**
Print usage statement Print usage statement

View File

@ -1,7 +1,6 @@
package registry package registry
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -24,11 +23,8 @@ func Login(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, er
// loginV1 tries to register/login to the v1 registry server. // loginV1 tries to register/login to the v1 registry server.
func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, error) { func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, error) {
var ( var (
status string err error
respBody []byte serverAddress = authConfig.ServerAddress
err error
respStatusCode = 0
serverAddress = authConfig.ServerAddress
) )
logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint) logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
@ -39,93 +35,37 @@ func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string,
loginAgainstOfficialIndex := serverAddress == IndexServer loginAgainstOfficialIndex := serverAddress == IndexServer
// to avoid sending the server address to the server it should be removed before being marshaled req, err := http.NewRequest("GET", serverAddress+"users/", nil)
authCopy := *authConfig req.SetBasicAuth(authConfig.Username, authConfig.Password)
authCopy.ServerAddress = "" resp, err := registryEndpoint.client.Do(req)
jsonBody, err := json.Marshal(authCopy)
if err != nil { if err != nil {
return "", fmt.Errorf("Config Error: %s", err) return "", err
} }
defer resp.Body.Close()
// using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. body, err := ioutil.ReadAll(resp.Body)
b := strings.NewReader(string(jsonBody))
resp1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
if err != nil { if err != nil {
return "", fmt.Errorf("Server Error: %s", err) return "", err
} }
defer resp1.Body.Close() if resp.StatusCode == http.StatusOK {
respStatusCode = resp1.StatusCode return "Login Succeeded", nil
respBody, err = ioutil.ReadAll(resp1.Body) } else if resp.StatusCode == http.StatusUnauthorized {
if err != nil {
return "", fmt.Errorf("Server Error: [%#v] %s", respStatusCode, err)
}
if respStatusCode == 201 {
if loginAgainstOfficialIndex { if loginAgainstOfficialIndex {
status = "Account created. Please use the confirmation link we sent" + return "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
" to your e-mail to activate it."
} else {
// *TODO: Use registry configuration to determine what this says, if anything?
status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it."
} }
} else if respStatusCode == 400 { return "", fmt.Errorf("Wrong login/password, please try again")
if string(respBody) == "\"Username or email already exists\"" { } else if resp.StatusCode == http.StatusForbidden {
req, err := http.NewRequest("GET", serverAddress+"users/", nil) if loginAgainstOfficialIndex {
req.SetBasicAuth(authConfig.Username, authConfig.Password) return "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode == 200 {
return "Login Succeeded", nil
} else if resp.StatusCode == 401 {
return "", fmt.Errorf("Wrong login/password, please try again")
} else if resp.StatusCode == 403 {
if loginAgainstOfficialIndex {
return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
}
// *TODO: Use registry configuration to determine what this says, if anything?
return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
} else if resp.StatusCode == 500 { // Issue #14326
logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
return "", fmt.Errorf("Internal Server Error")
}
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header)
}
return "", fmt.Errorf("Registration: %s", respBody)
} else if respStatusCode == 401 {
// This case would happen with private registries where /v1/users is
// protected, so people can use `docker login` as an auth check.
req, err := http.NewRequest("GET", serverAddress+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode == 200 {
return "Login Succeeded", nil
} else if resp.StatusCode == 401 {
return "", fmt.Errorf("Wrong login/password, please try again")
} else {
return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
resp.StatusCode, resp.Header)
} }
// *TODO: Use registry configuration to determine what this says, if anything?
return "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
return "", fmt.Errorf("Internal Server Error")
} else { } else {
return "", fmt.Errorf("Unexpected status code [%d] : %s", respStatusCode, respBody) return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
resp.StatusCode, resp.Header)
} }
return status, nil
} }
// loginV2 tries to login to the v2 registry server. The given registry endpoint has been // loginV2 tries to login to the v2 registry server. The given registry endpoint has been

View File

@ -14,7 +14,6 @@ func buildAuthConfigs() map[string]types.AuthConfig {
authConfigs[registry] = types.AuthConfig{ authConfigs[registry] = types.AuthConfig{
Username: "docker-user", Username: "docker-user",
Password: "docker-pass", Password: "docker-pass",
Email: "docker@docker.io",
} }
} }
@ -30,9 +29,6 @@ func TestSameAuthDataPostSave(t *testing.T) {
if authConfig.Password != "docker-pass" { if authConfig.Password != "docker-pass" {
t.Fail() t.Fail()
} }
if authConfig.Email != "docker@docker.io" {
t.Fail()
}
if authConfig.Auth != "" { if authConfig.Auth != "" {
t.Fail() t.Fail()
} }
@ -62,17 +58,14 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
registryAuth := types.AuthConfig{ registryAuth := types.AuthConfig{
Username: "foo-user", Username: "foo-user",
Password: "foo-pass", Password: "foo-pass",
Email: "foo@example.com",
} }
localAuth := types.AuthConfig{ localAuth := types.AuthConfig{
Username: "bar-user", Username: "bar-user",
Password: "bar-pass", Password: "bar-pass",
Email: "bar@example.com",
} }
officialAuth := types.AuthConfig{ officialAuth := types.AuthConfig{
Username: "baz-user", Username: "baz-user",
Password: "baz-pass", Password: "baz-pass",
Email: "baz@example.com",
} }
authConfigs[IndexServer] = officialAuth authConfigs[IndexServer] = officialAuth
@ -105,7 +98,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
for configKey, registries := range validRegistries { for configKey, registries := range validRegistries {
configured, ok := expectedAuths[configKey] configured, ok := expectedAuths[configKey]
if !ok || configured.Email == "" { if !ok {
t.Fail() t.Fail()
} }
index := &registrytypes.IndexInfo{ index := &registrytypes.IndexInfo{
@ -114,13 +107,13 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
for _, registry := range registries { for _, registry := range registries {
authConfigs[registry] = configured authConfigs[registry] = configured
resolved := ResolveAuthConfig(authConfigs, index) resolved := ResolveAuthConfig(authConfigs, index)
if resolved.Email != configured.Email { if resolved.Username != configured.Username || resolved.Password != configured.Password {
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) t.Errorf("%s -> %v != %v\n", registry, resolved, configured)
} }
delete(authConfigs, registry) delete(authConfigs, registry)
resolved = ResolveAuthConfig(authConfigs, index) resolved = ResolveAuthConfig(authConfigs, index)
if resolved.Email == configured.Email { if resolved.Username == configured.Username || resolved.Password == configured.Password {
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) t.Errorf("%s -> %v == %v\n", registry, resolved, configured)
} }
} }
} }

View File

@ -752,7 +752,6 @@ func (r *Session) GetAuthConfig(withPasswd bool) *types.AuthConfig {
return &types.AuthConfig{ return &types.AuthConfig{
Username: r.authConfig.Username, Username: r.authConfig.Username,
Password: password, Password: password,
Email: r.authConfig.Email,
} }
} }