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
=== modified file 'identityprovider/models/account.py'
--- identityprovider/models/account.py 2012-01-18 16:24:23 +0000
+++ identityprovider/models/account.py 2012-02-02 17:13:33 +0000
@@ -312,6 +312,12 @@
312 except Consumer.DoesNotExist:312 except Consumer.DoesNotExist:
313 return []313 return []
314314
315 def has_twofactor_devices(self):
316 """Returns True if this Account has any associated two-factor
317 devices (including one-time pads)."""
318 # TODO: Test for one-time pads.
319 return self.devices.exists()
320
315 def save(self, force_insert=False, force_update=False, **kwargs):321 def save(self, force_insert=False, force_update=False, **kwargs):
316 if settings.READ_ONLY_MODE:322 if settings.READ_ONLY_MODE:
317 return323 return
318324
=== modified file 'identityprovider/tests/unit/test_devices.py'
--- identityprovider/tests/unit/test_devices.py 2012-01-27 12:31:00 +0000
+++ identityprovider/tests/unit/test_devices.py 2012-02-02 17:13:33 +0000
@@ -252,6 +252,30 @@
252252
253 self.assertEqual(AuthenticationDevice.objects.all().count(), 0)253 self.assertEqual(AuthenticationDevice.objects.all().count(), 0)
254254
255 def test_preference_reset(self):
256 account = self._get_account()
257 account.twofactor_required = True
258 mock_request = self._get_mock_request()
259 mock_request.user = account
260 mock_request.method = 'POST'
261
262 device1 = AuthenticationDevice.objects.create(
263 account=account,
264 key='some key',
265 name='First device'
266 )
267 device2 = AuthenticationDevice.objects.create(
268 account=account,
269 key='some key',
270 name='Second device'
271 )
272
273 device_removal(mock_request, device1.id)
274 self.assertTrue(account.twofactor_required)
275
276 device_removal(mock_request, device2.id)
277 self.assertFalse(account.twofactor_required)
278
255279
256class TestPrefs(TwoFactorTestCase):280class TestPrefs(TwoFactorTestCase):
257281
258282
=== modified file 'identityprovider/tests/unit/test_views_ui.py'
--- identityprovider/tests/unit/test_views_ui.py 2012-01-30 14:42:31 +0000
+++ identityprovider/tests/unit/test_views_ui.py 2012-02-02 17:13:33 +0000
@@ -1196,6 +1196,20 @@
1196 oath = inputs[0]1196 oath = inputs[0]
1197 self.assertEqual(oath.attrib['autocomplete'], 'off')1197 self.assertEqual(oath.attrib['autocomplete'], 'off')
11981198
1199 def test_user_requires_twofactor_auth(self):
1200 def f(require2f, has_devices, returns, logs):
1201 with patch('logging.warning') as mock_warning:
1202 account = Mock()
1203 account.twofactor_required = require2f
1204 account.has_twofactor_devices.return_value = has_devices
1205 r = ui.user_requires_twofactor_auth(account)
1206 self.assertEqual(returns, r)
1207 self.assertEqual(logs, mock_warning.called)
1208 f(require2f=False, has_devices=False, returns=False, logs=False)
1209 f(require2f=False, has_devices=True, returns=False, logs=False)
1210 f(require2f=True, has_devices=False, returns=False, logs=True)
1211 f(require2f=True, has_devices=True, returns=True, logs=False)
1212
1199 @patch('identityprovider.views.ui.user_requires_twofactor_auth')1213 @patch('identityprovider.views.ui.user_requires_twofactor_auth')
1200 def test_when_user_requires_twofactor_redirected(self, mock_user):1214 def test_when_user_requires_twofactor_redirected(self, mock_user):
1201 mock_user.return_value = True1215 mock_user.return_value = True
12021216
=== modified file 'identityprovider/views/devices.py'
--- identityprovider/views/devices.py 2012-01-30 14:42:31 +0000
+++ identityprovider/views/devices.py 2012-02-02 17:13:33 +0000
@@ -201,9 +201,15 @@
201 ))201 ))
202202
203 device.delete()203 device.delete()
204
204 # We should probably send an e-mail to the user stating which205 # We should probably send an e-mail to the user stating which
205 # device was removed. As a security measure, this would be much206 # device was removed. As a security measure, this would be much
206 # stronger if bugs #784813, #784817, and #784818 were done.207 # stronger if bugs #784813, #784817, and #784818 were done.
208
209 if not request.user.has_twofactor_devices():
210 request.user.twofactor_required = False
211 request.user.save()
212
207 return HttpResponseSeeOther('/device-list')213 return HttpResponseSeeOther('/device-list')
208214
209215
210216
=== modified file 'identityprovider/views/ui.py'
--- identityprovider/views/ui.py 2012-01-30 14:42:31 +0000
+++ identityprovider/views/ui.py 2012-02-02 17:13:33 +0000
@@ -102,7 +102,11 @@
102def user_requires_twofactor_auth(account):102def user_requires_twofactor_auth(account):
103 if not gargoyle.is_active('TWOFACTOR'):103 if not gargoyle.is_active('TWOFACTOR'):
104 return False104 return False
105 return account.twofactor_required105 if account.twofactor_required:
106 if account.has_twofactor_devices():
107 return True
108 logging.warning('Account %s requires two-factor but has no devices' % account.openid_identifier)
109 return False
106110
107111
108112