github.com/jen20/docker@v1.13.1/cliconfig/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/docker-credential-helpers/client"
    12  	"github.com/docker/docker-credential-helpers/credentials"
    13  	"github.com/docker/docker/api/types"
    14  )
    15  
    16  const (
    17  	validServerAddress   = "https://index.docker.io/v1"
    18  	validServerAddress2  = "https://example.com:5002"
    19  	invalidServerAddress = "https://foobar.example.com"
    20  	missingCredsAddress  = "https://missing.docker.io/v1"
    21  )
    22  
    23  var errCommandExited = fmt.Errorf("exited 1")
    24  
    25  // mockCommand simulates interactions between the docker client and a remote
    26  // credentials helper.
    27  // Unit tests inject this mocked command into the remote to control execution.
    28  type mockCommand struct {
    29  	arg   string
    30  	input io.Reader
    31  }
    32  
    33  // Output returns responses from the remote credentials helper.
    34  // It mocks those responses based in the input in the mock.
    35  func (m *mockCommand) Output() ([]byte, error) {
    36  	in, err := ioutil.ReadAll(m.input)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	inS := string(in)
    41  
    42  	switch m.arg {
    43  	case "erase":
    44  		switch inS {
    45  		case validServerAddress:
    46  			return nil, nil
    47  		default:
    48  			return []byte("program failed"), errCommandExited
    49  		}
    50  	case "get":
    51  		switch inS {
    52  		case validServerAddress:
    53  			return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
    54  		case validServerAddress2:
    55  			return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
    56  		case missingCredsAddress:
    57  			return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
    58  		case invalidServerAddress:
    59  			return []byte("program failed"), errCommandExited
    60  		}
    61  	case "store":
    62  		var c credentials.Credentials
    63  		err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
    64  		if err != nil {
    65  			return []byte("program failed"), errCommandExited
    66  		}
    67  		switch c.ServerURL {
    68  		case validServerAddress:
    69  			return nil, nil
    70  		default:
    71  			return []byte("program failed"), errCommandExited
    72  		}
    73  	case "list":
    74  		return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
    75  	}
    76  
    77  	return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
    78  }
    79  
    80  // Input sets the input to send to a remote credentials helper.
    81  func (m *mockCommand) Input(in io.Reader) {
    82  	m.input = in
    83  }
    84  
    85  func mockCommandFn(args ...string) client.Program {
    86  	return &mockCommand{
    87  		arg: args[0],
    88  	}
    89  }
    90  
    91  func TestNativeStoreAddCredentials(t *testing.T) {
    92  	f := newConfigFile(make(map[string]types.AuthConfig))
    93  	f.CredentialsStore = "mock"
    94  
    95  	s := &nativeStore{
    96  		programFunc: mockCommandFn,
    97  		fileStore:   NewFileStore(f),
    98  	}
    99  	err := s.Store(types.AuthConfig{
   100  		Username:      "foo",
   101  		Password:      "bar",
   102  		Email:         "foo@example.com",
   103  		ServerAddress: validServerAddress,
   104  	})
   105  
   106  	if err != nil {
   107  		t.Fatal(err)
   108  	}
   109  
   110  	if len(f.AuthConfigs) != 1 {
   111  		t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
   112  	}
   113  
   114  	a, ok := f.AuthConfigs[validServerAddress]
   115  	if !ok {
   116  		t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
   117  	}
   118  	if a.Auth != "" {
   119  		t.Fatalf("expected auth to be empty, got %s", a.Auth)
   120  	}
   121  	if a.Username != "" {
   122  		t.Fatalf("expected username to be empty, got %s", a.Username)
   123  	}
   124  	if a.Password != "" {
   125  		t.Fatalf("expected password to be empty, got %s", a.Password)
   126  	}
   127  	if a.IdentityToken != "" {
   128  		t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
   129  	}
   130  	if a.Email != "foo@example.com" {
   131  		t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
   132  	}
   133  }
   134  
   135  func TestNativeStoreAddInvalidCredentials(t *testing.T) {
   136  	f := newConfigFile(make(map[string]types.AuthConfig))
   137  	f.CredentialsStore = "mock"
   138  
   139  	s := &nativeStore{
   140  		programFunc: mockCommandFn,
   141  		fileStore:   NewFileStore(f),
   142  	}
   143  	err := s.Store(types.AuthConfig{
   144  		Username:      "foo",
   145  		Password:      "bar",
   146  		Email:         "foo@example.com",
   147  		ServerAddress: invalidServerAddress,
   148  	})
   149  
   150  	if err == nil {
   151  		t.Fatal("expected error, got nil")
   152  	}
   153  
   154  	if !strings.Contains(err.Error(), "program failed") {
   155  		t.Fatalf("expected `program failed`, got %v", err)
   156  	}
   157  
   158  	if len(f.AuthConfigs) != 0 {
   159  		t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
   160  	}
   161  }
   162  
   163  func TestNativeStoreGet(t *testing.T) {
   164  	f := newConfigFile(map[string]types.AuthConfig{
   165  		validServerAddress: {
   166  			Email: "foo@example.com",
   167  		},
   168  	})
   169  	f.CredentialsStore = "mock"
   170  
   171  	s := &nativeStore{
   172  		programFunc: mockCommandFn,
   173  		fileStore:   NewFileStore(f),
   174  	}
   175  	a, err := s.Get(validServerAddress)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	if a.Username != "foo" {
   181  		t.Fatalf("expected username `foo`, got %s", a.Username)
   182  	}
   183  	if a.Password != "bar" {
   184  		t.Fatalf("expected password `bar`, got %s", a.Password)
   185  	}
   186  	if a.IdentityToken != "" {
   187  		t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
   188  	}
   189  	if a.Email != "foo@example.com" {
   190  		t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
   191  	}
   192  }
   193  
   194  func TestNativeStoreGetIdentityToken(t *testing.T) {
   195  	f := newConfigFile(map[string]types.AuthConfig{
   196  		validServerAddress2: {
   197  			Email: "foo@example2.com",
   198  		},
   199  	})
   200  	f.CredentialsStore = "mock"
   201  
   202  	s := &nativeStore{
   203  		programFunc: mockCommandFn,
   204  		fileStore:   NewFileStore(f),
   205  	}
   206  	a, err := s.Get(validServerAddress2)
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	if a.Username != "" {
   212  		t.Fatalf("expected username to be empty, got %s", a.Username)
   213  	}
   214  	if a.Password != "" {
   215  		t.Fatalf("expected password to be empty, got %s", a.Password)
   216  	}
   217  	if a.IdentityToken != "abcd1234" {
   218  		t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken)
   219  	}
   220  	if a.Email != "foo@example2.com" {
   221  		t.Fatalf("expected email `foo@example2.com`, got %s", a.Email)
   222  	}
   223  }
   224  
   225  func TestNativeStoreGetAll(t *testing.T) {
   226  	f := newConfigFile(map[string]types.AuthConfig{
   227  		validServerAddress: {
   228  			Email: "foo@example.com",
   229  		},
   230  	})
   231  	f.CredentialsStore = "mock"
   232  
   233  	s := &nativeStore{
   234  		programFunc: mockCommandFn,
   235  		fileStore:   NewFileStore(f),
   236  	}
   237  	as, err := s.GetAll()
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  
   242  	if len(as) != 2 {
   243  		t.Fatalf("wanted 2, got %d", len(as))
   244  	}
   245  
   246  	if as[validServerAddress].Username != "foo" {
   247  		t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
   248  	}
   249  	if as[validServerAddress].Password != "bar" {
   250  		t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
   251  	}
   252  	if as[validServerAddress].IdentityToken != "" {
   253  		t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
   254  	}
   255  	if as[validServerAddress].Email != "foo@example.com" {
   256  		t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
   257  	}
   258  	if as[validServerAddress2].Username != "" {
   259  		t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
   260  	}
   261  	if as[validServerAddress2].Password != "" {
   262  		t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
   263  	}
   264  	if as[validServerAddress2].IdentityToken != "abcd1234" {
   265  		t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
   266  	}
   267  	if as[validServerAddress2].Email != "" {
   268  		t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
   269  	}
   270  }
   271  
   272  func TestNativeStoreGetMissingCredentials(t *testing.T) {
   273  	f := newConfigFile(map[string]types.AuthConfig{
   274  		validServerAddress: {
   275  			Email: "foo@example.com",
   276  		},
   277  	})
   278  	f.CredentialsStore = "mock"
   279  
   280  	s := &nativeStore{
   281  		programFunc: mockCommandFn,
   282  		fileStore:   NewFileStore(f),
   283  	}
   284  	_, err := s.Get(missingCredsAddress)
   285  	if err != nil {
   286  		// missing credentials do not produce an error
   287  		t.Fatal(err)
   288  	}
   289  }
   290  
   291  func TestNativeStoreGetInvalidAddress(t *testing.T) {
   292  	f := newConfigFile(map[string]types.AuthConfig{
   293  		validServerAddress: {
   294  			Email: "foo@example.com",
   295  		},
   296  	})
   297  	f.CredentialsStore = "mock"
   298  
   299  	s := &nativeStore{
   300  		programFunc: mockCommandFn,
   301  		fileStore:   NewFileStore(f),
   302  	}
   303  	_, err := s.Get(invalidServerAddress)
   304  	if err == nil {
   305  		t.Fatal("expected error, got nil")
   306  	}
   307  
   308  	if !strings.Contains(err.Error(), "program failed") {
   309  		t.Fatalf("expected `program failed`, got %v", err)
   310  	}
   311  }
   312  
   313  func TestNativeStoreErase(t *testing.T) {
   314  	f := newConfigFile(map[string]types.AuthConfig{
   315  		validServerAddress: {
   316  			Email: "foo@example.com",
   317  		},
   318  	})
   319  	f.CredentialsStore = "mock"
   320  
   321  	s := &nativeStore{
   322  		programFunc: mockCommandFn,
   323  		fileStore:   NewFileStore(f),
   324  	}
   325  	err := s.Erase(validServerAddress)
   326  	if err != nil {
   327  		t.Fatal(err)
   328  	}
   329  
   330  	if len(f.AuthConfigs) != 0 {
   331  		t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
   332  	}
   333  }
   334  
   335  func TestNativeStoreEraseInvalidAddress(t *testing.T) {
   336  	f := newConfigFile(map[string]types.AuthConfig{
   337  		validServerAddress: {
   338  			Email: "foo@example.com",
   339  		},
   340  	})
   341  	f.CredentialsStore = "mock"
   342  
   343  	s := &nativeStore{
   344  		programFunc: mockCommandFn,
   345  		fileStore:   NewFileStore(f),
   346  	}
   347  	err := s.Erase(invalidServerAddress)
   348  	if err == nil {
   349  		t.Fatal("expected error, got nil")
   350  	}
   351  
   352  	if !strings.Contains(err.Error(), "program failed") {
   353  		t.Fatalf("expected `program failed`, got %v", err)
   354  	}
   355  }