credentials_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. package cmd
  2. import (
  3. "os"
  4. "path/filepath"
  5. "strings"
  6. "testing"
  7. )
  8. // writeEnv creates a .env in dir and returns its path.
  9. func writeEnv(t *testing.T, dir, content string) string {
  10. t.Helper()
  11. path := filepath.Join(dir, envFile)
  12. if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
  13. t.Fatalf("write .env: %v", err)
  14. }
  15. return path
  16. }
  17. // inDir runs fn with the working directory set to dir.
  18. func inDir(t *testing.T, dir string, fn func()) {
  19. t.Helper()
  20. old, err := os.Getwd()
  21. if err != nil {
  22. t.Fatalf("getwd: %v", err)
  23. }
  24. if err := os.Chdir(dir); err != nil {
  25. t.Fatalf("chdir: %v", err)
  26. }
  27. t.Cleanup(func() {
  28. if err := os.Chdir(old); err != nil {
  29. t.Fatalf("restore cwd: %v", err)
  30. }
  31. })
  32. fn()
  33. }
  34. // clearEnv removes every credential variable for the duration of the test.
  35. func clearEnv(t *testing.T) {
  36. t.Helper()
  37. for _, k := range []string{envUser, envPassword, envContext, envToken, envEndpoint} {
  38. t.Setenv(k, "")
  39. if err := os.Unsetenv(k); err != nil {
  40. t.Fatalf("unset %s: %v", k, err)
  41. }
  42. }
  43. }
  44. func TestLoadReadsEnvFile(t *testing.T) {
  45. dir := t.TempDir()
  46. writeEnv(t, dir, "SCHLUNDTECH_USER=u\nSCHLUNDTECH_PASSWORD=p\nSCHLUNDTECH_CONTEXT=10\n")
  47. clearEnv(t)
  48. inDir(t, dir, func() {
  49. creds, warning, err := loadCredentials()
  50. if err != nil {
  51. t.Fatalf("Load: %v", err)
  52. }
  53. if creds.User != "u" || creds.Password != "p" || creds.Context != "10" {
  54. t.Errorf("creds = %+v", creds)
  55. }
  56. if warning != "" {
  57. t.Errorf("warning = %q, want none for a 0600 file", warning)
  58. }
  59. })
  60. }
  61. // The real environment must win, so a single invocation can be redirected
  62. // without editing the file.
  63. func TestEnvironmentWinsOverEnvFile(t *testing.T) {
  64. dir := t.TempDir()
  65. writeEnv(t, dir, "SCHLUNDTECH_USER=from-file\nSCHLUNDTECH_PASSWORD=p\nSCHLUNDTECH_CONTEXT=10\n")
  66. clearEnv(t)
  67. t.Setenv(envUser, "from-environment")
  68. inDir(t, dir, func() {
  69. creds, _, err := loadCredentials()
  70. if err != nil {
  71. t.Fatalf("Load: %v", err)
  72. }
  73. if creds.User != "from-environment" {
  74. t.Errorf("user = %q, want the environment to win", creds.User)
  75. }
  76. })
  77. }
  78. // A missing .env is fine: the environment alone is enough.
  79. func TestMissingEnvFileIsNotAnError(t *testing.T) {
  80. dir := t.TempDir()
  81. clearEnv(t)
  82. t.Setenv(envUser, "u")
  83. t.Setenv(envPassword, "p")
  84. t.Setenv(envContext, "10")
  85. inDir(t, dir, func() {
  86. creds, warning, err := loadCredentials()
  87. if err != nil {
  88. t.Fatalf("Load: %v", err)
  89. }
  90. if creds.User != "u" {
  91. t.Errorf("user = %q", creds.User)
  92. }
  93. if warning != "" {
  94. t.Errorf("warning = %q, want none", warning)
  95. }
  96. })
  97. }
  98. // The file holds a password, so a permissive mode deserves a warning.
  99. func TestLoosePermissionsWarn(t *testing.T) {
  100. if os.Getuid() == 0 {
  101. t.Skip("running as root: the permission check would not be meaningful")
  102. }
  103. dir := t.TempDir()
  104. path := writeEnv(t, dir, "SCHLUNDTECH_USER=u\nSCHLUNDTECH_PASSWORD=p\nSCHLUNDTECH_CONTEXT=10\n")
  105. if err := os.Chmod(path, 0o644); err != nil {
  106. t.Fatalf("chmod: %v", err)
  107. }
  108. clearEnv(t)
  109. inDir(t, dir, func() {
  110. _, warning, err := loadCredentials()
  111. if err != nil {
  112. t.Fatalf("Load: %v", err)
  113. }
  114. if !strings.Contains(warning, "chmod 600") {
  115. t.Errorf("warning = %q, want it to suggest chmod 600", warning)
  116. }
  117. })
  118. }
  119. func TestMissingVariablesAreAllReported(t *testing.T) {
  120. clearEnv(t)
  121. _, err := credentialsFromEnv()
  122. if err == nil {
  123. t.Fatal("expected an error with nothing set")
  124. }
  125. msg := err.Error()
  126. for _, want := range []string{envUser, envPassword, envContext} {
  127. if !strings.Contains(msg, want) {
  128. t.Errorf("error %q does not mention %s", msg, want)
  129. }
  130. }
  131. // The message must point at the file, not just the variables.
  132. if !strings.Contains(msg, envFile) {
  133. t.Errorf("error %q does not mention %s", msg, envFile)
  134. }
  135. }
  136. // A password in the file must never leak into an error message.
  137. func TestErrorDoesNotLeakPassword(t *testing.T) {
  138. dir := t.TempDir()
  139. writeEnv(t, dir, "SCHLUNDTECH_PASSWORD=hunter2-do-not-print\n")
  140. clearEnv(t)
  141. inDir(t, dir, func() {
  142. _, _, err := loadCredentials()
  143. if err == nil {
  144. t.Fatal("expected an error")
  145. }
  146. if strings.Contains(err.Error(), "hunter2-do-not-print") {
  147. t.Errorf("error leaked the password: %q", err)
  148. }
  149. })
  150. }
  151. func TestMalformedEnvFileIsReported(t *testing.T) {
  152. dir := t.TempDir()
  153. writeEnv(t, dir, "this is not a valid line\n")
  154. clearEnv(t)
  155. inDir(t, dir, func() {
  156. _, _, err := loadCredentials()
  157. if err == nil {
  158. t.Fatal("expected a parse error")
  159. }
  160. if !strings.Contains(err.Error(), envFile) {
  161. t.Errorf("error = %q, want it to name the file", err)
  162. }
  163. })
  164. }
  165. func TestOptionalVariablesMayStayEmpty(t *testing.T) {
  166. clearEnv(t)
  167. t.Setenv(envUser, "u")
  168. t.Setenv(envPassword, "p")
  169. t.Setenv(envContext, "10")
  170. creds, err := credentialsFromEnv()
  171. if err != nil {
  172. t.Fatalf("FromEnv: %v", err)
  173. }
  174. if creds.Token != "" {
  175. t.Errorf("token = %q, want empty", creds.Token)
  176. }
  177. if creds.Endpoint != "" {
  178. t.Errorf("endpoint = %q, want empty", creds.Endpoint)
  179. }
  180. }