2016-12-25 14:31:52 -05:00
|
|
|
package credentials
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/docker/docker-credential-helpers/client"
|
|
|
|
"github.com/docker/docker-credential-helpers/credentials"
|
|
|
|
"github.com/docker/docker/api/types"
|
2017-03-09 13:23:45 -05:00
|
|
|
"github.com/pkg/errors"
|
2016-12-25 14:31:52 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
validServerAddress = "https://index.docker.io/v1"
|
|
|
|
validServerAddress2 = "https://example.com:5002"
|
|
|
|
invalidServerAddress = "https://foobar.example.com"
|
|
|
|
missingCredsAddress = "https://missing.docker.io/v1"
|
|
|
|
)
|
|
|
|
|
2017-03-09 13:23:45 -05:00
|
|
|
var errCommandExited = errors.Errorf("exited 1")
|
2016-12-25 14:31:52 -05:00
|
|
|
|
|
|
|
// mockCommand simulates interactions between the docker client and a remote
|
|
|
|
// credentials helper.
|
|
|
|
// Unit tests inject this mocked command into the remote to control execution.
|
|
|
|
type mockCommand struct {
|
|
|
|
arg string
|
|
|
|
input io.Reader
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output returns responses from the remote credentials helper.
|
|
|
|
// It mocks those responses based in the input in the mock.
|
|
|
|
func (m *mockCommand) Output() ([]byte, error) {
|
|
|
|
in, err := ioutil.ReadAll(m.input)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
inS := string(in)
|
|
|
|
|
|
|
|
switch m.arg {
|
|
|
|
case "erase":
|
|
|
|
switch inS {
|
|
|
|
case validServerAddress:
|
|
|
|
return nil, nil
|
|
|
|
default:
|
|
|
|
return []byte("program failed"), errCommandExited
|
|
|
|
}
|
|
|
|
case "get":
|
|
|
|
switch inS {
|
|
|
|
case validServerAddress:
|
|
|
|
return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
|
|
|
|
case validServerAddress2:
|
|
|
|
return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
|
|
|
|
case missingCredsAddress:
|
|
|
|
return []byte(credentials.NewErrCredentialsNotFound().Error()), errCommandExited
|
|
|
|
case invalidServerAddress:
|
|
|
|
return []byte("program failed"), errCommandExited
|
|
|
|
}
|
|
|
|
case "store":
|
|
|
|
var c credentials.Credentials
|
|
|
|
err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
|
|
|
|
if err != nil {
|
|
|
|
return []byte("program failed"), errCommandExited
|
|
|
|
}
|
|
|
|
switch c.ServerURL {
|
|
|
|
case validServerAddress:
|
|
|
|
return nil, nil
|
|
|
|
default:
|
|
|
|
return []byte("program failed"), errCommandExited
|
|
|
|
}
|
|
|
|
case "list":
|
|
|
|
return []byte(fmt.Sprintf(`{"%s": "%s", "%s": "%s"}`, validServerAddress, "foo", validServerAddress2, "<token>")), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
|
|
|
|
}
|
|
|
|
|
|
|
|
// Input sets the input to send to a remote credentials helper.
|
|
|
|
func (m *mockCommand) Input(in io.Reader) {
|
|
|
|
m.input = in
|
|
|
|
}
|
|
|
|
|
|
|
|
func mockCommandFn(args ...string) client.Program {
|
|
|
|
return &mockCommand{
|
|
|
|
arg: args[0],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNativeStoreAddCredentials(t *testing.T) {
|
|
|
|
f := newConfigFile(make(map[string]types.AuthConfig))
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
err := s.Store(types.AuthConfig{
|
|
|
|
Username: "foo",
|
|
|
|
Password: "bar",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: validServerAddress,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(f.AuthConfigs) != 1 {
|
|
|
|
t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
|
|
|
|
}
|
|
|
|
|
|
|
|
a, ok := f.AuthConfigs[validServerAddress]
|
|
|
|
if !ok {
|
|
|
|
t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
|
|
|
|
}
|
|
|
|
if a.Auth != "" {
|
|
|
|
t.Fatalf("expected auth to be empty, got %s", a.Auth)
|
|
|
|
}
|
|
|
|
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 != "" {
|
|
|
|
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 TestNativeStoreAddInvalidCredentials(t *testing.T) {
|
|
|
|
f := newConfigFile(make(map[string]types.AuthConfig))
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
err := s.Store(types.AuthConfig{
|
|
|
|
Username: "foo",
|
|
|
|
Password: "bar",
|
|
|
|
Email: "foo@example.com",
|
|
|
|
ServerAddress: invalidServerAddress,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error, got nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(err.Error(), "program failed") {
|
|
|
|
t.Fatalf("expected `program failed`, got %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(f.AuthConfigs) != 0 {
|
|
|
|
t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNativeStoreGet(t *testing.T) {
|
|
|
|
f := newConfigFile(map[string]types.AuthConfig{
|
|
|
|
validServerAddress: {
|
|
|
|
Email: "foo@example.com",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
a, err := s.Get(validServerAddress)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.Username != "foo" {
|
|
|
|
t.Fatalf("expected username `foo`, got %s", a.Username)
|
|
|
|
}
|
|
|
|
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{
|
|
|
|
programFunc: 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: {
|
|
|
|
Email: "foo@example.com",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
as, err := s.GetAll()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(as) != 2 {
|
|
|
|
t.Fatalf("wanted 2, got %d", len(as))
|
|
|
|
}
|
|
|
|
|
|
|
|
if as[validServerAddress].Username != "foo" {
|
|
|
|
t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
|
|
|
|
}
|
|
|
|
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 != "" {
|
|
|
|
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].IdentityToken != "abcd1234" {
|
|
|
|
t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
|
|
|
|
}
|
|
|
|
if as[validServerAddress2].Email != "" {
|
|
|
|
t.Fatalf("expected no email for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNativeStoreGetMissingCredentials(t *testing.T) {
|
|
|
|
f := newConfigFile(map[string]types.AuthConfig{
|
|
|
|
validServerAddress: {
|
|
|
|
Email: "foo@example.com",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
_, err := s.Get(missingCredsAddress)
|
|
|
|
if err != nil {
|
|
|
|
// missing credentials do not produce an error
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNativeStoreGetInvalidAddress(t *testing.T) {
|
|
|
|
f := newConfigFile(map[string]types.AuthConfig{
|
|
|
|
validServerAddress: {
|
|
|
|
Email: "foo@example.com",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
_, err := s.Get(invalidServerAddress)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error, got nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(err.Error(), "program failed") {
|
|
|
|
t.Fatalf("expected `program failed`, got %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNativeStoreErase(t *testing.T) {
|
|
|
|
f := newConfigFile(map[string]types.AuthConfig{
|
|
|
|
validServerAddress: {
|
|
|
|
Email: "foo@example.com",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
err := s.Erase(validServerAddress)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(f.AuthConfigs) != 0 {
|
|
|
|
t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNativeStoreEraseInvalidAddress(t *testing.T) {
|
|
|
|
f := newConfigFile(map[string]types.AuthConfig{
|
|
|
|
validServerAddress: {
|
|
|
|
Email: "foo@example.com",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
f.CredentialsStore = "mock"
|
|
|
|
|
|
|
|
s := &nativeStore{
|
|
|
|
programFunc: mockCommandFn,
|
|
|
|
fileStore: NewFileStore(f),
|
|
|
|
}
|
|
|
|
err := s.Erase(invalidServerAddress)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatal("expected error, got nil")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !strings.Contains(err.Error(), "program failed") {
|
|
|
|
t.Fatalf("expected `program failed`, got %v", err)
|
|
|
|
}
|
|
|
|
}
|