123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- // Copyright (C) 2019-2022 Nicola Murino
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published
- // by the Free Software Foundation, version 3.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- package mfa
- import (
- "testing"
- "time"
- "github.com/pquerna/otp"
- "github.com/pquerna/otp/totp"
- "github.com/stretchr/testify/assert"
- )
- func TestMFAConfig(t *testing.T) {
- config := Config{
- TOTP: []TOTPConfig{
- {},
- },
- }
- configName1 := "config1"
- configName2 := "config2"
- configName3 := "config3"
- err := config.Initialize()
- assert.Error(t, err)
- config.TOTP[0].Name = configName1
- err = config.Initialize()
- assert.Error(t, err)
- config.TOTP[0].Issuer = "issuer"
- err = config.Initialize()
- assert.Error(t, err)
- config.TOTP[0].Algo = TOTPAlgoSHA1
- err = config.Initialize()
- assert.NoError(t, err)
- config.TOTP = append(config.TOTP, TOTPConfig{
- Name: configName1,
- Issuer: "SFTPGo",
- Algo: TOTPAlgoSHA512,
- })
- err = config.Initialize()
- assert.Error(t, err)
- config.TOTP[1].Name = configName2
- err = config.Initialize()
- assert.NoError(t, err)
- assert.Len(t, GetAvailableTOTPConfigs(), 2)
- assert.Len(t, GetAvailableTOTPConfigNames(), 2)
- config.TOTP = append(config.TOTP, TOTPConfig{
- Name: configName3,
- Issuer: "SFTPGo",
- Algo: TOTPAlgoSHA256,
- })
- err = config.Initialize()
- assert.NoError(t, err)
- assert.Len(t, GetAvailableTOTPConfigs(), 3)
- if assert.Len(t, GetAvailableTOTPConfigNames(), 3) {
- assert.Contains(t, GetAvailableTOTPConfigNames(), configName1)
- assert.Contains(t, GetAvailableTOTPConfigNames(), configName2)
- assert.Contains(t, GetAvailableTOTPConfigNames(), configName3)
- }
- status := GetStatus()
- assert.True(t, status.IsActive)
- if assert.Len(t, status.TOTPConfigs, 3) {
- assert.Equal(t, configName1, status.TOTPConfigs[0].Name)
- assert.Equal(t, configName2, status.TOTPConfigs[1].Name)
- assert.Equal(t, configName3, status.TOTPConfigs[2].Name)
- }
- // now generate some secrets and validate some passcodes
- _, _, _, _, err = GenerateTOTPSecret("", "") //nolint:dogsled
- assert.Error(t, err)
- match, err := ValidateTOTPPasscode("", "", "")
- assert.Error(t, err)
- assert.False(t, match)
- cfgName, _, secret, _, err := GenerateTOTPSecret(configName1, "user1")
- assert.NoError(t, err)
- assert.NotEmpty(t, secret)
- assert.Equal(t, configName1, cfgName)
- passcode, err := generatePasscode(secret, otp.AlgorithmSHA1)
- assert.NoError(t, err)
- match, err = ValidateTOTPPasscode(configName1, passcode, secret)
- assert.NoError(t, err)
- assert.True(t, match)
- match, err = ValidateTOTPPasscode(configName1, passcode, secret)
- assert.ErrorIs(t, err, errPasscodeUsed)
- assert.False(t, match)
- passcode, err = generatePasscode(secret, otp.AlgorithmSHA256)
- assert.NoError(t, err)
- // config1 uses sha1 algo
- match, err = ValidateTOTPPasscode(configName1, passcode, secret)
- assert.NoError(t, err)
- assert.False(t, match)
- // config3 use the expected algo
- match, err = ValidateTOTPPasscode(configName3, passcode, secret)
- assert.NoError(t, err)
- assert.True(t, match)
- stopCleanupTicker()
- }
- func TestCleanupPasscodes(t *testing.T) {
- usedPasscodes.Store("key", time.Now().Add(-24*time.Hour).UTC())
- startCleanupTicker(30 * time.Millisecond)
- assert.Eventually(t, func() bool {
- _, ok := usedPasscodes.Load("key")
- return !ok
- }, 1000*time.Millisecond, 100*time.Millisecond)
- stopCleanupTicker()
- }
- func TestTOTPGenerateErrors(t *testing.T) {
- config := TOTPConfig{
- Name: "name",
- Issuer: "",
- algo: otp.AlgorithmSHA1,
- }
- // issuer cannot be empty
- _, _, _, err := config.generate("username", 200, 200) //nolint:dogsled
- assert.Error(t, err)
- config.Issuer = "issuer"
- // we cannot encode an image smaller than 45x45
- _, _, _, err = config.generate("username", 30, 30) //nolint:dogsled
- assert.Error(t, err)
- }
- func generatePasscode(secret string, algo otp.Algorithm) (string, error) {
- return totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
- Period: 30,
- Skew: 1,
- Digits: otp.DigitsSix,
- Algorithm: algo,
- })
- }
|