Merge lp:~canonical-isd-hackers/canonical-identity-provider/2f-reset into lp:canonical-identity-provider/release

Proposed by David Owen
Status: Merged
Approved by: Simon Davy
Approved revision: no longer in the source branch.
Merged at revision: 317
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/2f-reset
Merge into: lp:canonical-identity-provider/release
Diff against target: 110 lines (+55/-1)
5 files modified
identityprovider/models/account.py (+6/-0)
identityprovider/tests/unit/test_devices.py (+24/-0)
identityprovider/tests/unit/test_views_ui.py (+14/-0)
identityprovider/views/devices.py (+6/-0)
identityprovider/views/ui.py (+5/-1)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/2f-reset
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+91179@code.launchpad.net

Commit message

Reset the user's preference to "only when a site asks" when removing the last two-factor device. Additionally, if the flag is set for "required" during authentication but the account has no devices, log an error and proceed with normal (not two-factor) authentication.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/models/account.py'
2--- identityprovider/models/account.py 2012-01-18 16:24:23 +0000
3+++ identityprovider/models/account.py 2012-02-02 17:13:33 +0000
4@@ -312,6 +312,12 @@
5 except Consumer.DoesNotExist:
6 return []
7
8+ def has_twofactor_devices(self):
9+ """Returns True if this Account has any associated two-factor
10+ devices (including one-time pads)."""
11+ # TODO: Test for one-time pads.
12+ return self.devices.exists()
13+
14 def save(self, force_insert=False, force_update=False, **kwargs):
15 if settings.READ_ONLY_MODE:
16 return
17
18=== modified file 'identityprovider/tests/unit/test_devices.py'
19--- identityprovider/tests/unit/test_devices.py 2012-01-27 12:31:00 +0000
20+++ identityprovider/tests/unit/test_devices.py 2012-02-02 17:13:33 +0000
21@@ -252,6 +252,30 @@
22
23 self.assertEqual(AuthenticationDevice.objects.all().count(), 0)
24
25+ def test_preference_reset(self):
26+ account = self._get_account()
27+ account.twofactor_required = True
28+ mock_request = self._get_mock_request()
29+ mock_request.user = account
30+ mock_request.method = 'POST'
31+
32+ device1 = AuthenticationDevice.objects.create(
33+ account=account,
34+ key='some key',
35+ name='First device'
36+ )
37+ device2 = AuthenticationDevice.objects.create(
38+ account=account,
39+ key='some key',
40+ name='Second device'
41+ )
42+
43+ device_removal(mock_request, device1.id)
44+ self.assertTrue(account.twofactor_required)
45+
46+ device_removal(mock_request, device2.id)
47+ self.assertFalse(account.twofactor_required)
48+
49
50 class TestPrefs(TwoFactorTestCase):
51
52
53=== modified file 'identityprovider/tests/unit/test_views_ui.py'
54--- identityprovider/tests/unit/test_views_ui.py 2012-01-30 14:42:31 +0000
55+++ identityprovider/tests/unit/test_views_ui.py 2012-02-02 17:13:33 +0000
56@@ -1196,6 +1196,20 @@
57 oath = inputs[0]
58 self.assertEqual(oath.attrib['autocomplete'], 'off')
59
60+ def test_user_requires_twofactor_auth(self):
61+ def f(require2f, has_devices, returns, logs):
62+ with patch('logging.warning') as mock_warning:
63+ account = Mock()
64+ account.twofactor_required = require2f
65+ account.has_twofactor_devices.return_value = has_devices
66+ r = ui.user_requires_twofactor_auth(account)
67+ self.assertEqual(returns, r)
68+ self.assertEqual(logs, mock_warning.called)
69+ f(require2f=False, has_devices=False, returns=False, logs=False)
70+ f(require2f=False, has_devices=True, returns=False, logs=False)
71+ f(require2f=True, has_devices=False, returns=False, logs=True)
72+ f(require2f=True, has_devices=True, returns=True, logs=False)
73+
74 @patch('identityprovider.views.ui.user_requires_twofactor_auth')
75 def test_when_user_requires_twofactor_redirected(self, mock_user):
76 mock_user.return_value = True
77
78=== modified file 'identityprovider/views/devices.py'
79--- identityprovider/views/devices.py 2012-01-30 14:42:31 +0000
80+++ identityprovider/views/devices.py 2012-02-02 17:13:33 +0000
81@@ -201,9 +201,15 @@
82 ))
83
84 device.delete()
85+
86 # We should probably send an e-mail to the user stating which
87 # device was removed. As a security measure, this would be much
88 # stronger if bugs #784813, #784817, and #784818 were done.
89+
90+ if not request.user.has_twofactor_devices():
91+ request.user.twofactor_required = False
92+ request.user.save()
93+
94 return HttpResponseSeeOther('/device-list')
95
96
97
98=== modified file 'identityprovider/views/ui.py'
99--- identityprovider/views/ui.py 2012-01-30 14:42:31 +0000
100+++ identityprovider/views/ui.py 2012-02-02 17:13:33 +0000
101@@ -102,7 +102,11 @@
102 def user_requires_twofactor_auth(account):
103 if not gargoyle.is_active('TWOFACTOR'):
104 return False
105- return account.twofactor_required
106+ if account.twofactor_required:
107+ if account.has_twofactor_devices():
108+ return True
109+ logging.warning('Account %s requires two-factor but has no devices' % account.openid_identifier)
110+ return False
111
112
113