github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/services/mfa/mfa_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package mfa
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"net/url"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/dgryski/dgoogauth"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"github.com/mattermost/mattermost-server/v5/model"
    18  	"github.com/mattermost/mattermost-server/v5/plugin/plugintest/mock"
    19  	"github.com/mattermost/mattermost-server/v5/store/storetest/mocks"
    20  	"github.com/mattermost/mattermost-server/v5/utils/testutils"
    21  )
    22  
    23  func TestGenerateSecret(t *testing.T) {
    24  	user := &model.User{Id: model.NewId(), Roles: "system_user"}
    25  
    26  	config := model.Config{}
    27  	config.SetDefaults()
    28  	config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true)
    29  	configService := testutils.StaticConfigService{Cfg: &config}
    30  
    31  	t.Run("fail on disabled mfa", func(t *testing.T) {
    32  		wrongConfig := model.Config{}
    33  		wrongConfig.SetDefaults()
    34  		wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false)
    35  		wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig}
    36  		mfa := New(wrongConfigService, nil)
    37  		_, _, err := mfa.GenerateSecret(user)
    38  		require.NotNil(t, err)
    39  		require.Equal(t, "mfa.mfa_disabled.app_error", err.Id)
    40  	})
    41  
    42  	t.Run("fail on store action fail", func(t *testing.T) {
    43  		storeMock := mocks.Store{}
    44  		userStoreMock := mocks.UserStore{}
    45  		userStoreMock.On("UpdateMfaSecret", user.Id, mock.AnythingOfType("string")).Return(func(userId string, secret string) error {
    46  			return errors.New("failed to update mfa secret")
    47  		})
    48  		storeMock.On("User").Return(&userStoreMock)
    49  
    50  		mfa := New(configService, &storeMock)
    51  		_, _, err := mfa.GenerateSecret(user)
    52  		require.NotNil(t, err)
    53  		require.Equal(t, "mfa.generate_qr_code.save_secret.app_error", err.Id)
    54  	})
    55  
    56  	t.Run("Successful generate secret", func(t *testing.T) {
    57  		storeMock := mocks.Store{}
    58  		userStoreMock := mocks.UserStore{}
    59  		userStoreMock.On("UpdateMfaSecret", user.Id, mock.AnythingOfType("string")).Return(func(userId string, secret string) error {
    60  			return nil
    61  		})
    62  		storeMock.On("User").Return(&userStoreMock)
    63  
    64  		mfa := New(configService, &storeMock)
    65  
    66  		secret, img, err := mfa.GenerateSecret(user)
    67  		require.Nil(t, err)
    68  		assert.Len(t, secret, 32)
    69  		require.NotEmpty(t, img, "no image set")
    70  	})
    71  }
    72  
    73  func TestGetIssuerFromUrl(t *testing.T) {
    74  	cases := []struct {
    75  		Input    string
    76  		Expected string
    77  	}{
    78  		{"http://somewebsite.com", url.QueryEscape("somewebsite.com")},
    79  		{"https://somewebsite.com", url.QueryEscape("somewebsite.com")},
    80  		{"https://some.website.com", url.QueryEscape("some.website.com")},
    81  		{" https://www.somewebsite.com", url.QueryEscape("somewebsite.com")},
    82  		{"http://somewebsite.com/chat", url.QueryEscape("somewebsite.com/chat")},
    83  		{"somewebsite.com ", url.QueryEscape("somewebsite.com")},
    84  		{"http://localhost:8065", url.QueryEscape("localhost:8065")},
    85  		{"", "Mattermost"},
    86  		{"  ", "Mattermost"},
    87  	}
    88  
    89  	for _, c := range cases {
    90  		assert.Equal(t, c.Expected, getIssuerFromUrl(c.Input))
    91  	}
    92  }
    93  
    94  func TestActivate(t *testing.T) {
    95  	user := &model.User{Id: model.NewId(), Roles: "system_user"}
    96  	user.MfaSecret = model.NewRandomBase32String(MFASecretSize)
    97  
    98  	token := dgoogauth.ComputeCode(user.MfaSecret, time.Now().UTC().Unix()/30)
    99  
   100  	config := model.Config{}
   101  	config.SetDefaults()
   102  	config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true)
   103  	configService := testutils.StaticConfigService{Cfg: &config}
   104  
   105  	t.Run("fail on disabled mfa", func(t *testing.T) {
   106  		wrongConfig := model.Config{}
   107  		wrongConfig.SetDefaults()
   108  		wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false)
   109  		wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig}
   110  		mfa := New(wrongConfigService, nil)
   111  		err := mfa.Activate(user, "not-important")
   112  		require.NotNil(t, err)
   113  		require.Equal(t, "mfa.mfa_disabled.app_error", err.Id)
   114  	})
   115  
   116  	t.Run("fail on wrongly formatted token", func(t *testing.T) {
   117  		mfa := New(configService, nil)
   118  		err := mfa.Activate(user, "invalid-token")
   119  		require.NotNil(t, err)
   120  		require.Equal(t, "mfa.activate.authenticate.app_error", err.Id)
   121  	})
   122  
   123  	t.Run("fail on invalid token", func(t *testing.T) {
   124  		mfa := New(configService, nil)
   125  		err := mfa.Activate(user, "000000")
   126  		require.NotNil(t, err)
   127  		require.Equal(t, "mfa.activate.bad_token.app_error", err.Id)
   128  	})
   129  
   130  	t.Run("fail on store action fail", func(t *testing.T) {
   131  		storeMock := mocks.Store{}
   132  		userStoreMock := mocks.UserStore{}
   133  		userStoreMock.On("UpdateMfaActive", user.Id, true).Return(func(userId string, active bool) error {
   134  			return errors.New("failed to update mfa active")
   135  		})
   136  		storeMock.On("User").Return(&userStoreMock)
   137  
   138  		mfa := New(configService, &storeMock)
   139  		err := mfa.Activate(user, fmt.Sprintf("%06d", token))
   140  		require.NotNil(t, err)
   141  		require.Equal(t, "mfa.activate.save_active.app_error", err.Id)
   142  	})
   143  
   144  	t.Run("Successful activate", func(t *testing.T) {
   145  		storeMock := mocks.Store{}
   146  		userStoreMock := mocks.UserStore{}
   147  		userStoreMock.On("UpdateMfaActive", user.Id, true).Return(func(userId string, active bool) error {
   148  			return nil
   149  		})
   150  		storeMock.On("User").Return(&userStoreMock)
   151  		mfa := New(configService, &storeMock)
   152  
   153  		err := mfa.Activate(user, fmt.Sprintf("%06d", token))
   154  		require.Nil(t, err)
   155  	})
   156  }
   157  
   158  func TestDeactivate(t *testing.T) {
   159  	user := &model.User{Id: model.NewId(), Roles: "system_user"}
   160  
   161  	config := model.Config{}
   162  	config.SetDefaults()
   163  	config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true)
   164  	configService := testutils.StaticConfigService{Cfg: &config}
   165  
   166  	t.Run("fail on disabled mfa", func(t *testing.T) {
   167  		wrongConfig := model.Config{}
   168  		wrongConfig.SetDefaults()
   169  		wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false)
   170  		wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig}
   171  		mfa := New(wrongConfigService, nil)
   172  		err := mfa.Deactivate(user.Id)
   173  		require.NotNil(t, err)
   174  		require.Equal(t, "mfa.mfa_disabled.app_error", err.Id)
   175  	})
   176  
   177  	t.Run("fail on store UpdateMfaActive action fail", func(t *testing.T) {
   178  		storeMock := mocks.Store{}
   179  		userStoreMock := mocks.UserStore{}
   180  		userStoreMock.On("UpdateMfaActive", user.Id, false).Return(func(userId string, active bool) error {
   181  			return errors.New("failed to update mfa active")
   182  		})
   183  		userStoreMock.On("UpdateMfaSecret", user.Id, "").Return(func(userId string, secret string) error {
   184  			return errors.New("failed to update mfa secret")
   185  		})
   186  		storeMock.On("User").Return(&userStoreMock)
   187  
   188  		mfa := New(configService, &storeMock)
   189  		err := mfa.Deactivate(user.Id)
   190  		require.NotNil(t, err)
   191  		require.Equal(t, "mfa.deactivate.save_active.app_error", err.Id)
   192  	})
   193  
   194  	t.Run("fail on store UpdateMfaSecret action fail", func(t *testing.T) {
   195  		storeMock := mocks.Store{}
   196  		userStoreMock := mocks.UserStore{}
   197  		userStoreMock.On("UpdateMfaActive", user.Id, false).Return(func(userId string, active bool) error {
   198  			return nil
   199  		})
   200  		userStoreMock.On("UpdateMfaSecret", user.Id, "").Return(func(userId string, secret string) error {
   201  			return errors.New("failed to update mfa secret")
   202  		})
   203  		storeMock.On("User").Return(&userStoreMock)
   204  
   205  		mfa := New(configService, &storeMock)
   206  		err := mfa.Deactivate(user.Id)
   207  		require.NotNil(t, err)
   208  		require.Equal(t, "mfa.deactivate.save_secret.app_error", err.Id)
   209  	})
   210  
   211  	t.Run("Successful deactivate", func(t *testing.T) {
   212  		storeMock := mocks.Store{}
   213  		userStoreMock := mocks.UserStore{}
   214  		userStoreMock.On("UpdateMfaActive", user.Id, false).Return(func(userId string, active bool) error {
   215  			return nil
   216  		})
   217  		userStoreMock.On("UpdateMfaSecret", user.Id, "").Return(func(userId string, secret string) error {
   218  			return nil
   219  		})
   220  		storeMock.On("User").Return(&userStoreMock)
   221  		mfa := New(configService, &storeMock)
   222  
   223  		err := mfa.Deactivate(user.Id)
   224  		require.Nil(t, err)
   225  	})
   226  }
   227  
   228  func TestValidateToken(t *testing.T) {
   229  	secret := model.NewRandomBase32String(MFASecretSize)
   230  	token := dgoogauth.ComputeCode(secret, time.Now().UTC().Unix()/30)
   231  
   232  	config := model.Config{}
   233  	config.SetDefaults()
   234  	config.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(true)
   235  	configService := testutils.StaticConfigService{Cfg: &config}
   236  
   237  	t.Run("fail on disabled mfa", func(t *testing.T) {
   238  		wrongConfig := model.Config{}
   239  		wrongConfig.SetDefaults()
   240  		wrongConfig.ServiceSettings.EnableMultifactorAuthentication = model.NewBool(false)
   241  		wrongConfigService := testutils.StaticConfigService{Cfg: &wrongConfig}
   242  		mfa := New(wrongConfigService, nil)
   243  		ok, err := mfa.ValidateToken(secret, fmt.Sprintf("%06d", token))
   244  		require.NotNil(t, err)
   245  		require.False(t, ok)
   246  		require.Equal(t, "mfa.mfa_disabled.app_error", err.Id)
   247  	})
   248  
   249  	t.Run("fail on wrongly formatted token", func(t *testing.T) {
   250  		mfa := New(configService, nil)
   251  		ok, err := mfa.ValidateToken(secret, "invalid-token")
   252  		require.NotNil(t, err)
   253  		require.False(t, ok)
   254  		require.Equal(t, "mfa.validate_token.authenticate.app_error", err.Id)
   255  	})
   256  
   257  	t.Run("fail on invalid token", func(t *testing.T) {
   258  		mfa := New(configService, nil)
   259  		ok, err := mfa.ValidateToken(secret, "000000")
   260  		require.Nil(t, err)
   261  		require.False(t, ok)
   262  	})
   263  
   264  	t.Run("valid token", func(t *testing.T) {
   265  		mfa := New(configService, nil)
   266  		ok, err := mfa.ValidateToken(secret, fmt.Sprintf("%06d", token))
   267  		require.Nil(t, err)
   268  		require.True(t, ok)
   269  	})
   270  }