diff --git a/api/client/info.go b/api/client/info.go index 2d02af3a58..2959424378 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -93,8 +93,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error { u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username if len(u) > 0 { fmt.Fprintf(cli.out, "Username: %v\n", u) - fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) } + fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) } // Only output these warnings if the server does not support these features diff --git a/cliconfig/credentials/native_store.go b/cliconfig/credentials/native_store.go index 2da041d4b2..9b8997dd64 100644 --- a/cliconfig/credentials/native_store.go +++ b/cliconfig/credentials/native_store.go @@ -13,7 +13,10 @@ import ( "github.com/docker/engine-api/types" ) -const remoteCredentialsPrefix = "docker-credential-" +const ( + remoteCredentialsPrefix = "docker-credential-" + tokenUsername = "" +) // Standarize the not found error, so every helper returns // the same message and docker can handle it properly. @@ -29,14 +32,14 @@ type command interface { type credentialsRequest struct { ServerURL string Username string - Password string + Secret string } // credentialsGetResponse is the information serialized from a remote store // when the plugin sends requests to get the user credentials. type credentialsGetResponse struct { Username string - Password string + Secret string } // nativeStore implements a credentials store @@ -76,6 +79,7 @@ func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) { return auth, err } auth.Username = creds.Username + auth.IdentityToken = creds.IdentityToken auth.Password = creds.Password return auth, nil @@ -89,6 +93,7 @@ func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) { creds, _ := c.getCredentialsFromStore(s) ac.Username = creds.Username ac.Password = creds.Password + ac.IdentityToken = creds.IdentityToken auths[s] = ac } @@ -102,6 +107,7 @@ func (c *nativeStore) Store(authConfig types.AuthConfig) error { } authConfig.Username = "" authConfig.Password = "" + authConfig.IdentityToken = "" // Fallback to old credential in plain text to save only the email return c.fileStore.Store(authConfig) @@ -113,7 +119,12 @@ func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error { creds := &credentialsRequest{ ServerURL: config.ServerAddress, Username: config.Username, - Password: config.Password, + Secret: config.Password, + } + + if config.IdentityToken != "" { + creds.Username = tokenUsername + creds.Secret = config.IdentityToken } buffer := new(bytes.Buffer) @@ -158,13 +169,18 @@ func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthC return ret, err } - ret.Username = resp.Username - ret.Password = resp.Password + if resp.Username == tokenUsername { + ret.IdentityToken = resp.Secret + } else { + ret.Password = resp.Secret + ret.Username = resp.Username + } + ret.ServerAddress = serverAddress return ret, nil } -// eraseCredentialsFromStore executes the command to remove the server redentails from the native store. +// eraseCredentialsFromStore executes the command to remove the server credentails from the native store. func (c *nativeStore) eraseCredentialsFromStore(serverURL string) error { cmd := c.commandFn("erase") cmd.Input(strings.NewReader(serverURL)) diff --git a/cliconfig/credentials/native_store_test.go b/cliconfig/credentials/native_store_test.go index 454fd0bd91..354221027e 100644 --- a/cliconfig/credentials/native_store_test.go +++ b/cliconfig/credentials/native_store_test.go @@ -47,8 +47,10 @@ func (m *mockCommand) Output() ([]byte, error) { } case "get": switch inS { - case validServerAddress, validServerAddress2: - return []byte(`{"Username": "foo", "Password": "bar"}`), nil + case validServerAddress: + return []byte(`{"Username": "foo", "Secret": "bar"}`), nil + case validServerAddress2: + return []byte(`{"Username": "", "Secret": "abcd1234"}`), nil case missingCredsAddress: return []byte(errCredentialsNotFound.Error()), errCommandExited case invalidServerAddress: @@ -118,6 +120,9 @@ func TestNativeStoreAddCredentials(t *testing.T) { if a.Password != "" { t.Fatalf("expected password to be empty, got %s", a.Password) } + if a.IdentityToken != "" { + t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken) + } if a.Email != "foo@example.com" { t.Fatalf("expected email `foo@example.com`, got %s", a.Email) } @@ -174,11 +179,45 @@ func TestNativeStoreGet(t *testing.T) { if a.Password != "bar" { t.Fatalf("expected password `bar`, got %s", a.Password) } + if a.IdentityToken != "" { + t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken) + } if a.Email != "foo@example.com" { t.Fatalf("expected email `foo@example.com`, got %s", a.Email) } } +func TestNativeStoreGetIdentityToken(t *testing.T) { + f := newConfigFile(map[string]types.AuthConfig{ + validServerAddress2: { + Email: "foo@example2.com", + }, + }) + f.CredentialsStore = "mock" + + s := &nativeStore{ + commandFn: mockCommandFn, + fileStore: NewFileStore(f), + } + a, err := s.Get(validServerAddress2) + if err != nil { + t.Fatal(err) + } + + if a.Username != "" { + t.Fatalf("expected username to be empty, got %s", a.Username) + } + if a.Password != "" { + t.Fatalf("expected password to be empty, got %s", a.Password) + } + if a.IdentityToken != "abcd1234" { + t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken) + } + if a.Email != "foo@example2.com" { + t.Fatalf("expected email `foo@example2.com`, got %s", a.Email) + } +} + func TestNativeStoreGetAll(t *testing.T) { f := newConfigFile(map[string]types.AuthConfig{ validServerAddress: { @@ -209,14 +248,20 @@ func TestNativeStoreGetAll(t *testing.T) { if as[validServerAddress].Password != "bar" { t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password) } + if as[validServerAddress].IdentityToken != "" { + t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken) + } if as[validServerAddress].Email != "foo@example.com" { t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email) } - if as[validServerAddress2].Username != "foo" { - t.Fatalf("expected username `foo` for %s, got %s", validServerAddress2, as[validServerAddress2].Username) + if as[validServerAddress2].Username != "" { + t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username) } - if as[validServerAddress2].Password != "bar" { - t.Fatalf("expected password `bar` for %s, got %s", validServerAddress2, as[validServerAddress2].Password) + if as[validServerAddress2].Password != "" { + t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password) + } + if as[validServerAddress2].IdentityToken != "abcd1234" { + t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken) } if as[validServerAddress2].Email != "foo@example2.com" { t.Fatalf("expected email `foo@example2.com` for %s, got %s", validServerAddress2, as[validServerAddress2].Email) diff --git a/docs/reference/commandline/login.md b/docs/reference/commandline/login.md index 34a7228427..c92c97156f 100644 --- a/docs/reference/commandline/login.md +++ b/docs/reference/commandline/login.md @@ -78,17 +78,20 @@ The helpers always use the first argument in the command to identify the action. There are only three possible values for that argument: `store`, `get`, and `erase`. The `store` command takes a JSON payload from the standard input. That payload carries -the server address, to identify the credential, the user name and the password. -This is an example of that payload: +the server address, to identify the credential, the user name, and either a password +or an identity token. ```json { "ServerURL": "https://index.docker.io/v1", "Username": "david", - "Password": "passw0rd1" + "Secret": "passw0rd1" } ``` +If the secret being stored is an identity token, the Username should be set to +``. + The `store` command can write error messages to `STDOUT` that the docker engine will show if there was an issue. @@ -102,7 +105,7 @@ and password from this payload: ```json { "Username": "david", - "Password": "passw0rd1" + "Secret": "passw0rd1" } ``` diff --git a/integration-cli/fixtures/auth/docker-credential-shell-test b/integration-cli/fixtures/auth/docker-credential-shell-test index 0c94bcd216..1980bb1803 100755 --- a/integration-cli/fixtures/auth/docker-credential-shell-test +++ b/integration-cli/fixtures/auth/docker-credential-shell-test @@ -8,8 +8,8 @@ case $1 in server=$(echo "$in" | jq --raw-output ".ServerURL" | sha1sum - | awk '{print $1}') username=$(echo "$in" | jq --raw-output ".Username") - password=$(echo "$in" | jq --raw-output ".Password") - echo "{ \"Username\": \"${username}\", \"Password\": \"${password}\" }" > $TEMP/$server + password=$(echo "$in" | jq --raw-output ".Secret") + echo "{ \"Username\": \"${username}\", \"Secret\": \"${password}\" }" > $TEMP/$server ;; "get") in=$(