native_store_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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/engine-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. }
  67. return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errCommandExited
  68. }
  69. // Input sets the input to send to a remote credentials helper.
  70. func (m *mockCommand) Input(in io.Reader) {
  71. m.input = in
  72. }
  73. func mockCommandFn(args ...string) client.Program {
  74. return &mockCommand{
  75. arg: args[0],
  76. }
  77. }
  78. func TestNativeStoreAddCredentials(t *testing.T) {
  79. f := newConfigFile(make(map[string]types.AuthConfig))
  80. f.CredentialsStore = "mock"
  81. s := &nativeStore{
  82. programFunc: mockCommandFn,
  83. fileStore: NewFileStore(f),
  84. }
  85. err := s.Store(types.AuthConfig{
  86. Username: "foo",
  87. Password: "bar",
  88. Email: "foo@example.com",
  89. ServerAddress: validServerAddress,
  90. })
  91. if err != nil {
  92. t.Fatal(err)
  93. }
  94. if len(f.AuthConfigs) != 1 {
  95. t.Fatalf("expected 1 auth config, got %d", len(f.AuthConfigs))
  96. }
  97. a, ok := f.AuthConfigs[validServerAddress]
  98. if !ok {
  99. t.Fatalf("expected auth for %s, got %v", validServerAddress, f.AuthConfigs)
  100. }
  101. if a.Auth != "" {
  102. t.Fatalf("expected auth to be empty, got %s", a.Auth)
  103. }
  104. if a.Username != "" {
  105. t.Fatalf("expected username to be empty, got %s", a.Username)
  106. }
  107. if a.Password != "" {
  108. t.Fatalf("expected password to be empty, got %s", a.Password)
  109. }
  110. if a.IdentityToken != "" {
  111. t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
  112. }
  113. if a.Email != "foo@example.com" {
  114. t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
  115. }
  116. }
  117. func TestNativeStoreAddInvalidCredentials(t *testing.T) {
  118. f := newConfigFile(make(map[string]types.AuthConfig))
  119. f.CredentialsStore = "mock"
  120. s := &nativeStore{
  121. programFunc: mockCommandFn,
  122. fileStore: NewFileStore(f),
  123. }
  124. err := s.Store(types.AuthConfig{
  125. Username: "foo",
  126. Password: "bar",
  127. Email: "foo@example.com",
  128. ServerAddress: invalidServerAddress,
  129. })
  130. if err == nil {
  131. t.Fatal("expected error, got nil")
  132. }
  133. if !strings.Contains(err.Error(), "program failed") {
  134. t.Fatalf("expected `program failed`, got %v", err)
  135. }
  136. if len(f.AuthConfigs) != 0 {
  137. t.Fatalf("expected 0 auth config, got %d", len(f.AuthConfigs))
  138. }
  139. }
  140. func TestNativeStoreGet(t *testing.T) {
  141. f := newConfigFile(map[string]types.AuthConfig{
  142. validServerAddress: {
  143. Email: "foo@example.com",
  144. },
  145. })
  146. f.CredentialsStore = "mock"
  147. s := &nativeStore{
  148. programFunc: mockCommandFn,
  149. fileStore: NewFileStore(f),
  150. }
  151. a, err := s.Get(validServerAddress)
  152. if err != nil {
  153. t.Fatal(err)
  154. }
  155. if a.Username != "foo" {
  156. t.Fatalf("expected username `foo`, got %s", a.Username)
  157. }
  158. if a.Password != "bar" {
  159. t.Fatalf("expected password `bar`, got %s", a.Password)
  160. }
  161. if a.IdentityToken != "" {
  162. t.Fatalf("expected identity token to be empty, got %s", a.IdentityToken)
  163. }
  164. if a.Email != "foo@example.com" {
  165. t.Fatalf("expected email `foo@example.com`, got %s", a.Email)
  166. }
  167. }
  168. func TestNativeStoreGetIdentityToken(t *testing.T) {
  169. f := newConfigFile(map[string]types.AuthConfig{
  170. validServerAddress2: {
  171. Email: "foo@example2.com",
  172. },
  173. })
  174. f.CredentialsStore = "mock"
  175. s := &nativeStore{
  176. programFunc: mockCommandFn,
  177. fileStore: NewFileStore(f),
  178. }
  179. a, err := s.Get(validServerAddress2)
  180. if err != nil {
  181. t.Fatal(err)
  182. }
  183. if a.Username != "" {
  184. t.Fatalf("expected username to be empty, got %s", a.Username)
  185. }
  186. if a.Password != "" {
  187. t.Fatalf("expected password to be empty, got %s", a.Password)
  188. }
  189. if a.IdentityToken != "abcd1234" {
  190. t.Fatalf("expected identity token `abcd1234`, got %s", a.IdentityToken)
  191. }
  192. if a.Email != "foo@example2.com" {
  193. t.Fatalf("expected email `foo@example2.com`, got %s", a.Email)
  194. }
  195. }
  196. func TestNativeStoreGetAll(t *testing.T) {
  197. f := newConfigFile(map[string]types.AuthConfig{
  198. validServerAddress: {
  199. Email: "foo@example.com",
  200. },
  201. validServerAddress2: {
  202. Email: "foo@example2.com",
  203. },
  204. })
  205. f.CredentialsStore = "mock"
  206. s := &nativeStore{
  207. programFunc: mockCommandFn,
  208. fileStore: NewFileStore(f),
  209. }
  210. as, err := s.GetAll()
  211. if err != nil {
  212. t.Fatal(err)
  213. }
  214. if len(as) != 2 {
  215. t.Fatalf("wanted 2, got %d", len(as))
  216. }
  217. if as[validServerAddress].Username != "foo" {
  218. t.Fatalf("expected username `foo` for %s, got %s", validServerAddress, as[validServerAddress].Username)
  219. }
  220. if as[validServerAddress].Password != "bar" {
  221. t.Fatalf("expected password `bar` for %s, got %s", validServerAddress, as[validServerAddress].Password)
  222. }
  223. if as[validServerAddress].IdentityToken != "" {
  224. t.Fatalf("expected identity to be empty for %s, got %s", validServerAddress, as[validServerAddress].IdentityToken)
  225. }
  226. if as[validServerAddress].Email != "foo@example.com" {
  227. t.Fatalf("expected email `foo@example.com` for %s, got %s", validServerAddress, as[validServerAddress].Email)
  228. }
  229. if as[validServerAddress2].Username != "" {
  230. t.Fatalf("expected username to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Username)
  231. }
  232. if as[validServerAddress2].Password != "" {
  233. t.Fatalf("expected password to be empty for %s, got %s", validServerAddress2, as[validServerAddress2].Password)
  234. }
  235. if as[validServerAddress2].IdentityToken != "abcd1234" {
  236. t.Fatalf("expected identity token `abcd1324` for %s, got %s", validServerAddress2, as[validServerAddress2].IdentityToken)
  237. }
  238. if as[validServerAddress2].Email != "foo@example2.com" {
  239. t.Fatalf("expected email `foo@example2.com` for %s, got %s", validServerAddress2, as[validServerAddress2].Email)
  240. }
  241. }
  242. func TestNativeStoreGetMissingCredentials(t *testing.T) {
  243. f := newConfigFile(map[string]types.AuthConfig{
  244. validServerAddress: {
  245. Email: "foo@example.com",
  246. },
  247. })
  248. f.CredentialsStore = "mock"
  249. s := &nativeStore{
  250. programFunc: mockCommandFn,
  251. fileStore: NewFileStore(f),
  252. }
  253. _, err := s.Get(missingCredsAddress)
  254. if err != nil {
  255. // missing credentials do not produce an error
  256. t.Fatal(err)
  257. }
  258. }
  259. func TestNativeStoreGetInvalidAddress(t *testing.T) {
  260. f := newConfigFile(map[string]types.AuthConfig{
  261. validServerAddress: {
  262. Email: "foo@example.com",
  263. },
  264. })
  265. f.CredentialsStore = "mock"
  266. s := &nativeStore{
  267. programFunc: mockCommandFn,
  268. fileStore: NewFileStore(f),
  269. }
  270. _, err := s.Get(invalidServerAddress)
  271. if err == nil {
  272. t.Fatal("expected error, got nil")
  273. }
  274. if !strings.Contains(err.Error(), "program failed") {
  275. t.Fatalf("expected `program failed`, got %v", err)
  276. }
  277. }
  278. func TestNativeStoreErase(t *testing.T) {
  279. f := newConfigFile(map[string]types.AuthConfig{
  280. validServerAddress: {
  281. Email: "foo@example.com",
  282. },
  283. })
  284. f.CredentialsStore = "mock"
  285. s := &nativeStore{
  286. programFunc: mockCommandFn,
  287. fileStore: NewFileStore(f),
  288. }
  289. err := s.Erase(validServerAddress)
  290. if err != nil {
  291. t.Fatal(err)
  292. }
  293. if len(f.AuthConfigs) != 0 {
  294. t.Fatalf("expected 0 auth configs, got %d", len(f.AuthConfigs))
  295. }
  296. }
  297. func TestNativeStoreEraseInvalidAddress(t *testing.T) {
  298. f := newConfigFile(map[string]types.AuthConfig{
  299. validServerAddress: {
  300. Email: "foo@example.com",
  301. },
  302. })
  303. f.CredentialsStore = "mock"
  304. s := &nativeStore{
  305. programFunc: mockCommandFn,
  306. fileStore: NewFileStore(f),
  307. }
  308. err := s.Erase(invalidServerAddress)
  309. if err == nil {
  310. t.Fatal("expected error, got nil")
  311. }
  312. if !strings.Contains(err.Error(), "program failed") {
  313. t.Fatalf("expected `program failed`, got %v", err)
  314. }
  315. }