Skip to content

Commit e2b744c

Browse files
blagdpgaspar
andauthored
fix: Only update user.last_login on successful authentication (#1775)
* Only update user.last_login if they successfully authenticated * Update docstrings for update_user_auth_stat * Add tests for BaseSecurityManager.update_user_auth_stat Co-authored-by: Daniel Vaz Gaspar <[email protected]>
1 parent 557249f commit e2b744c

File tree

2 files changed

+78
-4
lines changed

2 files changed

+78
-4
lines changed

flask_appbuilder/security/manager.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -820,23 +820,28 @@ def reset_password(self, userid, password):
820820

821821
def update_user_auth_stat(self, user, success=True):
822822
"""
823-
Update authentication successful to user.
823+
Update user authentication stats upon successful/unsuccessful
824+
authentication attempts.
824825
825826
:param user:
826-
The authenticated user model
827+
The identified (but possibly not successfully authenticated) user
828+
model
827829
:param success:
828-
Default to true, if false increments fail_login_count on user model
830+
:type success: bool or None
831+
Defaults to true, if true increments login_count, updates
832+
last_login, and resets fail_login_count to 0, if false increments
833+
fail_login_count on user model.
829834
"""
830835
if not user.login_count:
831836
user.login_count = 0
832837
if not user.fail_login_count:
833838
user.fail_login_count = 0
834839
if success:
835840
user.login_count += 1
841+
user.last_login = datetime.datetime.now()
836842
user.fail_login_count = 0
837843
else:
838844
user.fail_login_count += 1
839-
user.last_login = datetime.datetime.now()
840845
self.update_user(user)
841846

842847
def auth_user_db(self, username, password):
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import datetime
2+
import unittest
3+
from unittest.mock import MagicMock, patch
4+
5+
from flask_appbuilder.security.manager import BaseSecurityManager
6+
7+
8+
@patch.object(BaseSecurityManager, "update_user")
9+
@patch.object(BaseSecurityManager, "__init__", return_value=None)
10+
class BaseSecurityManagerUpdateUserAuthStatTestCase(unittest.TestCase):
11+
def test_first_successful_auth(self, mock1, mock2):
12+
bsm = BaseSecurityManager()
13+
14+
user_mock = MagicMock()
15+
user_mock.login_count = None
16+
user_mock.fail_login_count = None
17+
user_mock.last_login = None
18+
19+
bsm.update_user_auth_stat(user_mock, success=True)
20+
21+
self.assertEqual(user_mock.login_count, 1)
22+
self.assertEqual(user_mock.fail_login_count, 0)
23+
self.assertEqual(type(user_mock.last_login), datetime.datetime)
24+
self.assertTrue(bsm.update_user.called_once)
25+
26+
def test_first_unsuccessful_auth(self, mock1, mock2):
27+
bsm = BaseSecurityManager()
28+
29+
user_mock = MagicMock()
30+
user_mock.login_count = None
31+
user_mock.fail_login_count = None
32+
user_mock.last_login = None
33+
34+
bsm.update_user_auth_stat(user_mock, success=False)
35+
36+
self.assertEqual(user_mock.login_count, 0)
37+
self.assertEqual(user_mock.fail_login_count, 1)
38+
self.assertEqual(user_mock.last_login, None)
39+
self.assertTrue(bsm.update_user.called_once)
40+
41+
def test_subsequent_successful_auth(self, mock1, mock2):
42+
bsm = BaseSecurityManager()
43+
44+
user_mock = MagicMock()
45+
user_mock.login_count = 5
46+
user_mock.fail_login_count = 9
47+
user_mock.last_login = None
48+
49+
bsm.update_user_auth_stat(user_mock, success=True)
50+
51+
self.assertEqual(user_mock.login_count, 6)
52+
self.assertEqual(user_mock.fail_login_count, 0)
53+
self.assertEqual(type(user_mock.last_login), datetime.datetime)
54+
self.assertTrue(bsm.update_user.called_once)
55+
56+
def test_subsequent_unsuccessful_auth(self, mock1, mock2):
57+
bsm = BaseSecurityManager()
58+
59+
user_mock = MagicMock()
60+
user_mock.login_count = 5
61+
user_mock.fail_login_count = 9
62+
user_mock.last_login = None
63+
64+
bsm.update_user_auth_stat(user_mock, success=False)
65+
66+
self.assertEqual(user_mock.login_count, 5)
67+
self.assertEqual(user_mock.fail_login_count, 10)
68+
self.assertEqual(user_mock.last_login, None)
69+
self.assertTrue(bsm.update_user.called_once)

0 commit comments

Comments
 (0)