mfa_test.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package mfa
  15. import (
  16. "testing"
  17. "time"
  18. "github.com/pquerna/otp"
  19. "github.com/pquerna/otp/totp"
  20. "github.com/stretchr/testify/assert"
  21. "github.com/stretchr/testify/require"
  22. )
  23. func TestMFAConfig(t *testing.T) {
  24. config := Config{
  25. TOTP: []TOTPConfig{
  26. {},
  27. },
  28. }
  29. configName1 := "config1"
  30. configName2 := "config2"
  31. configName3 := "config3"
  32. err := config.Initialize()
  33. assert.Error(t, err)
  34. config.TOTP[0].Name = configName1
  35. err = config.Initialize()
  36. assert.Error(t, err)
  37. config.TOTP[0].Issuer = "issuer"
  38. err = config.Initialize()
  39. assert.Error(t, err)
  40. config.TOTP[0].Algo = TOTPAlgoSHA1
  41. err = config.Initialize()
  42. assert.NoError(t, err)
  43. config.TOTP = append(config.TOTP, TOTPConfig{
  44. Name: configName1,
  45. Issuer: "SFTPGo",
  46. Algo: TOTPAlgoSHA512,
  47. })
  48. err = config.Initialize()
  49. assert.Error(t, err)
  50. config.TOTP[1].Name = configName2
  51. err = config.Initialize()
  52. assert.NoError(t, err)
  53. assert.Len(t, GetAvailableTOTPConfigs(), 2)
  54. assert.Len(t, GetAvailableTOTPConfigNames(), 2)
  55. config.TOTP = append(config.TOTP, TOTPConfig{
  56. Name: configName3,
  57. Issuer: "SFTPGo",
  58. Algo: TOTPAlgoSHA256,
  59. })
  60. err = config.Initialize()
  61. assert.NoError(t, err)
  62. assert.Len(t, GetAvailableTOTPConfigs(), 3)
  63. if assert.Len(t, GetAvailableTOTPConfigNames(), 3) {
  64. assert.Contains(t, GetAvailableTOTPConfigNames(), configName1)
  65. assert.Contains(t, GetAvailableTOTPConfigNames(), configName2)
  66. assert.Contains(t, GetAvailableTOTPConfigNames(), configName3)
  67. }
  68. status := GetStatus()
  69. assert.True(t, status.IsActive)
  70. if assert.Len(t, status.TOTPConfigs, 3) {
  71. assert.Equal(t, configName1, status.TOTPConfigs[0].Name)
  72. assert.Equal(t, configName2, status.TOTPConfigs[1].Name)
  73. assert.Equal(t, configName3, status.TOTPConfigs[2].Name)
  74. }
  75. // now generate some secrets and validate some passcodes
  76. _, _, _, err = GenerateTOTPSecret("", "") //nolint:dogsled
  77. assert.Error(t, err)
  78. match, err := ValidateTOTPPasscode("", "", "")
  79. assert.Error(t, err)
  80. assert.False(t, match)
  81. cfgName, key, _, err := GenerateTOTPSecret(configName1, "user1")
  82. assert.NoError(t, err)
  83. assert.NotEmpty(t, key.Secret())
  84. assert.Equal(t, configName1, cfgName)
  85. passcode, err := generatePasscode(key.Secret(), otp.AlgorithmSHA1)
  86. assert.NoError(t, err)
  87. match, err = ValidateTOTPPasscode(configName1, passcode, key.Secret())
  88. assert.NoError(t, err)
  89. assert.True(t, match)
  90. match, err = ValidateTOTPPasscode(configName1, passcode, key.Secret())
  91. assert.ErrorIs(t, err, errPasscodeUsed)
  92. assert.False(t, match)
  93. passcode, err = generatePasscode(key.Secret(), otp.AlgorithmSHA256)
  94. assert.NoError(t, err)
  95. // config1 uses sha1 algo
  96. match, err = ValidateTOTPPasscode(configName1, passcode, key.Secret())
  97. assert.NoError(t, err)
  98. assert.False(t, match)
  99. // config3 use the expected algo
  100. match, err = ValidateTOTPPasscode(configName3, passcode, key.Secret())
  101. assert.NoError(t, err)
  102. assert.True(t, match)
  103. stopCleanupTicker()
  104. }
  105. func TestGenerateQRCodeFromURL(t *testing.T) {
  106. _, err := GenerateQRCodeFromURL("http://foo\x7f.cloud", 200, 200)
  107. assert.Error(t, err)
  108. config := TOTPConfig{
  109. Name: "config name",
  110. Issuer: "SFTPGo",
  111. Algo: TOTPAlgoSHA256,
  112. }
  113. key, qrCode, err := config.generate("a", 150, 150)
  114. require.NoError(t, err)
  115. qrCode1, err := GenerateQRCodeFromURL(key.URL(), 150, 150)
  116. require.NoError(t, err)
  117. assert.Equal(t, qrCode, qrCode1)
  118. _, err = GenerateQRCodeFromURL(key.URL(), 10, 10)
  119. assert.Error(t, err)
  120. }
  121. func TestCleanupPasscodes(t *testing.T) {
  122. usedPasscodes.Store("key", time.Now().Add(-24*time.Hour).UTC())
  123. startCleanupTicker(30 * time.Millisecond)
  124. assert.Eventually(t, func() bool {
  125. _, ok := usedPasscodes.Load("key")
  126. return !ok
  127. }, 1000*time.Millisecond, 100*time.Millisecond)
  128. stopCleanupTicker()
  129. }
  130. func TestTOTPGenerateErrors(t *testing.T) {
  131. config := TOTPConfig{
  132. Name: "name",
  133. Issuer: "",
  134. algo: otp.AlgorithmSHA1,
  135. }
  136. // issuer cannot be empty
  137. _, _, err := config.generate("username", 200, 200) //nolint:dogsled
  138. assert.Error(t, err)
  139. config.Issuer = "issuer"
  140. // we cannot encode an image smaller than 45x45
  141. _, _, err = config.generate("username", 30, 30) //nolint:dogsled
  142. assert.Error(t, err)
  143. }
  144. func generatePasscode(secret string, algo otp.Algorithm) (string, error) {
  145. return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
  146. Period: 30,
  147. Skew: 1,
  148. Digits: otp.DigitsSix,
  149. Algorithm: algo,
  150. })
  151. }