瀏覽代碼

Add support for identity tokens in client credentials store

Update unit test and documentation to handle the new case where Username
is set to <token> to indicate an identity token is involved.

Change the "Password" field in communications with the credential helper
to "Secret" to make clear it has a more generic purpose.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann 9 年之前
父節點
當前提交
ba0aa5311a

+ 1 - 1
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

+ 23 - 7
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           = "<token>"
+)
 
 // 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))

+ 51 - 6
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": "<token>", "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 != "" {
+		t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
 	}
-	if as[validServerAddress2].Password != "bar" {
-		t.Fatalf("expected password `bar` 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)

+ 7 - 4
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
+`<token>`.
+
 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"
 }
 ```
 

+ 2 - 2
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=$(</dev/stdin)