github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/config/credentials/native_store_test.go (about)

     1  package credentials
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/docker/cli/cli/config/types"
    12  	"github.com/docker/docker-credential-helpers/client"
    13  	"github.com/docker/docker-credential-helpers/credentials"
    14  	"github.com/pkg/errors"
    15  	"gotest.tools/v3/assert"
    16  	is "gotest.tools/v3/assert/cmp"
    17  )
    18  
    19  const (
    20  	validServerAddress   = "https://index.docker.io/v1"
    21  	validServerAddress2  = "https://example.com:5002"
    22  	invalidServerAddress = "https://foobar.example.com"
    23  	missingCredsAddress  = "https://missing.docker.io/v1"
    24  )
    25  
    26  var errCommandExited = errors.Errorf("exited 1")
    27  
    28  // mockCommand simulates interactions between the docker client and a remote
    29  // credentials helper.
    30  // Unit tests inject this mocked command into the remote to control execution.
    31  type mockCommand struct {
    32  	arg   string
    33  	input io.Reader
    34  }
    35  
    36  // Output returns responses from the remote credentials helper.
    37  // It mocks those responses based in the input in the mock.
    38  func (m *mockCommand) Output() ([]byte, error) {
    39  	in, err := ioutil.ReadAll(m.input)
    40  	if err != nil {
    41  		return nil, err
    42  	}
    43  	inS := string(in)
    44  
    45  	switch m.arg {
    46  	case "erase":
    47  		switch inS {
    48  		case validServerAddress:
    49  			return nil, nil
    50  		default:
    51  			return []byte("program failed"), errCommandExited
    52  		}
    53  	case "get":
    54  		switch inS {
    55  		case validServerAddress:
    56  			return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
    57  		case validServerAddress2:
    58  			return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
    59  		case missingCredsAddress:
    60  			return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
    61  		case invalidServerAddress:
    62  			return []byte("program failed"), errCommandExited
    63  		}
    64  	case "store":
    65  		var c credentials.Credentials
    66  		err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
    67  		if err != nil {
    68  			return []byte("program failed"), errCommandExited
    69  		}
    70  		switch c.ServerURL {
    71  		case validServerAddress:
    72  			return nil, nil
    73  		default:
    74  			return []byte("program failed"), errCommandExited
    75  		}
    76  	case "list":
    77  		return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
    78  	}
    79  
    80  	return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
    81  }
    82  
    83  // Input sets the input to send to a remote credentials helper.
    84  func (m *mockCommand) Input(in io.Reader) {
    85  	m.input = in
    86  }
    87  
    88  func mockCommandFn(args ...string) client.Program {
    89  	return &mockCommand{
    90  		arg: args[0],
    91  	}
    92  }
    93  
    94  func TestNativeStoreAddCredentials(t *testing.T) {
    95  	f := newStore(make(map[string]types.AuthConfig))
    96  	s := &nativeStore{
    97  		programFunc: mockCommandFn,
    98  		fileStore:   NewFileStore(f),
    99  	}
   100  	auth := types.AuthConfig{
   101  		Username:      "foo",
   102  		Password:      "bar",
   103  		Email:         "foo@example.com",
   104  		ServerAddress: validServerAddress,
   105  	}
   106  	err := s.Store(auth)
   107  	assert.NilError(t, err)
   108  	assert.Check(t, is.Len(f.GetAuthConfigs(), 1))
   109  
   110  	actual, ok := f.GetAuthConfigs()[validServerAddress]
   111  	assert.Check(t, ok)
   112  	expected := types.AuthConfig{
   113  		Email:         auth.Email,
   114  		ServerAddress: auth.ServerAddress,
   115  	}
   116  	assert.Check(t, is.DeepEqual(expected, actual))
   117  }
   118  
   119  func TestNativeStoreAddInvalidCredentials(t *testing.T) {
   120  	f := newStore(make(map[string]types.AuthConfig))
   121  	s := &nativeStore{
   122  		programFunc: mockCommandFn,
   123  		fileStore:   NewFileStore(f),
   124  	}
   125  	err := s.Store(types.AuthConfig{
   126  		Username:      "foo",
   127  		Password:      "bar",
   128  		Email:         "foo@example.com",
   129  		ServerAddress: invalidServerAddress,
   130  	})
   131  	assert.ErrorContains(t, err, "program failed")
   132  	assert.Check(t, is.Len(f.GetAuthConfigs(), 0))
   133  }
   134  
   135  func TestNativeStoreGet(t *testing.T) {
   136  	f := newStore(map[string]types.AuthConfig{
   137  		validServerAddress: {
   138  			Email: "foo@example.com",
   139  		},
   140  	})
   141  	s := &nativeStore{
   142  		programFunc: mockCommandFn,
   143  		fileStore:   NewFileStore(f),
   144  	}
   145  	actual, err := s.Get(validServerAddress)
   146  	assert.NilError(t, err)
   147  
   148  	expected := types.AuthConfig{
   149  		Username: "foo",
   150  		Password: "bar",
   151  		Email:    "foo@example.com",
   152  	}
   153  	assert.Check(t, is.DeepEqual(expected, actual))
   154  }
   155  
   156  func TestNativeStoreGetIdentityToken(t *testing.T) {
   157  	f := newStore(map[string]types.AuthConfig{
   158  		validServerAddress2: {
   159  			Email: "foo@example2.com",
   160  		},
   161  	})
   162  
   163  	s := &nativeStore{
   164  		programFunc: mockCommandFn,
   165  		fileStore:   NewFileStore(f),
   166  	}
   167  	actual, err := s.Get(validServerAddress2)
   168  	assert.NilError(t, err)
   169  
   170  	expected := types.AuthConfig{
   171  		IdentityToken: "abcd1234",
   172  		Email:         "foo@example2.com",
   173  	}
   174  	assert.Check(t, is.DeepEqual(expected, actual))
   175  }
   176  
   177  func TestNativeStoreGetAll(t *testing.T) {
   178  	f := newStore(map[string]types.AuthConfig{
   179  		validServerAddress: {
   180  			Email: "foo@example.com",
   181  		},
   182  	})
   183  
   184  	s := &nativeStore{
   185  		programFunc: mockCommandFn,
   186  		fileStore:   NewFileStore(f),
   187  	}
   188  	as, err := s.GetAll()
   189  	assert.NilError(t, err)
   190  	assert.Check(t, is.Len(as, 2))
   191  
   192  	if as[validServerAddress].Username != "foo" {
   193  		t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
   194  	}
   195  	if as[validServerAddress].Password != "bar" {
   196  		t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
   197  	}
   198  	if as[validServerAddress].IdentityToken != "" {
   199  		t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
   200  	}
   201  	if as[validServerAddress].Email != "foo@example.com" {
   202  		t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
   203  	}
   204  	if as[validServerAddress2].Username != "" {
   205  		t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
   206  	}
   207  	if as[validServerAddress2].Password != "" {
   208  		t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
   209  	}
   210  	if as[validServerAddress2].IdentityToken != "abcd1234" {
   211  		t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
   212  	}
   213  	if as[validServerAddress2].Email != "" {
   214  		t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
   215  	}
   216  }
   217  
   218  func TestNativeStoreGetMissingCredentials(t *testing.T) {
   219  	f := newStore(map[string]types.AuthConfig{
   220  		validServerAddress: {
   221  			Email: "foo@example.com",
   222  		},
   223  	})
   224  
   225  	s := &nativeStore{
   226  		programFunc: mockCommandFn,
   227  		fileStore:   NewFileStore(f),
   228  	}
   229  	_, err := s.Get(missingCredsAddress)
   230  	assert.NilError(t, err)
   231  }
   232  
   233  func TestNativeStoreGetInvalidAddress(t *testing.T) {
   234  	f := newStore(map[string]types.AuthConfig{
   235  		validServerAddress: {
   236  			Email: "foo@example.com",
   237  		},
   238  	})
   239  
   240  	s := &nativeStore{
   241  		programFunc: mockCommandFn,
   242  		fileStore:   NewFileStore(f),
   243  	}
   244  	_, err := s.Get(invalidServerAddress)
   245  	assert.ErrorContains(t, err, "program failed")
   246  }
   247  
   248  func TestNativeStoreErase(t *testing.T) {
   249  	f := newStore(map[string]types.AuthConfig{
   250  		validServerAddress: {
   251  			Email: "foo@example.com",
   252  		},
   253  	})
   254  
   255  	s := &nativeStore{
   256  		programFunc: mockCommandFn,
   257  		fileStore:   NewFileStore(f),
   258  	}
   259  	err := s.Erase(validServerAddress)
   260  	assert.NilError(t, err)
   261  	assert.Check(t, is.Len(f.GetAuthConfigs(), 0))
   262  }
   263  
   264  func TestNativeStoreEraseInvalidAddress(t *testing.T) {
   265  	f := newStore(map[string]types.AuthConfig{
   266  		validServerAddress: {
   267  			Email: "foo@example.com",
   268  		},
   269  	})
   270  
   271  	s := &nativeStore{
   272  		programFunc: mockCommandFn,
   273  		fileStore:   NewFileStore(f),
   274  	}
   275  	err := s.Erase(invalidServerAddress)
   276  	assert.ErrorContains(t, err, "program failed")
   277  }