native_store_test.go 8.9 KB

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