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 }