Merge lp:~maxiberta/canonical-identity-provider/fix-authn-device-inline-form-type into lp:canonical-identity-provider/release

Proposed by Maximiliano Bertacchini
Status: Merged
Approved by: Maximiliano Bertacchini
Approved revision: no longer in the source branch.
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: lp:~maxiberta/canonical-identity-provider/fix-authn-device-inline-form-type
Merge into: lp:canonical-identity-provider/release
Diff against target: 101 lines (+6/-29)
2 files modified
src/identityprovider/admin.py (+1/-11)
src/identityprovider/tests/test_admin.py (+5/-18)
To merge this branch: bzr merge lp:~maxiberta/canonical-identity-provider/fix-authn-device-inline-form-type
Reviewer Review Type Date Requested Status
Daniel Manrique (community) Approve
Review via email: mp+336566@code.launchpad.net

Commit message

Make admin AuthenticationDeviceInlineForm.device_type readonly instead of a choice field (LP: #1745156).

Description of the change

Make admin AuthenticationDeviceInlineForm.device_type a free text input instead of a choice field. The choice field was introduced a long time ago (LP: #1071778) but doesn't seem to make too much sense now, based on the fact that `AuthenticationDevice.device_type` is a `TextField(null=True)`, and the variety of values currently found:

sso_staging=> SELECT COUNT(*), device_type FROM identityprovider_authenticationdevice GROUP BY device_type;
 count | device_type
-------+--------------
    94 |
   114 | paper
     1 | yubi:hotp
   123 | google
    66 | google:totp
     2 | google:hotp
    25 | yubi
     2 | None
     2 | generic:hotp
   372 | generic
(10 rows)

To post a comment you must log in.
Revision history for this message
Daniel Manrique (roadmr) wrote :

This should work, I made 2 comments related to usability but since this is pretty broken right now, we could even go with this code and refine things later.

review: Approve
Revision history for this message
Daniel Manrique (roadmr) wrote :

Replying to myself: Maxi suggested making the field read-only, which makes both my comments moot: if the field is non-editable, there's not much need for the help text I suggested, nor for the expansion of choices to allow using the selector.

Changing the type of device for an on-the-field device will completely change its behavior and essentially render it useless for the user, so it's not something we've ever done. Usually we just delete devices and let users recreate them. The most common admin operations are fiddling with the counter and *maybe* extracting the random key, which can still be done. The main point is to be able to save changed records, which can still be done if the field is read-only.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/identityprovider/admin.py'
2--- src/identityprovider/admin.py 2016-12-06 16:53:22 +0000
3+++ src/identityprovider/admin.py 2018-01-24 19:16:25 +0000
4@@ -53,15 +53,6 @@
5 )
6
7
8-DEVICE_TYPE_CHOICES = [
9- (None, 'Unset'),
10- ('paper', 'Printable backup codes'),
11- ('yubi', 'YubiKey'),
12- ('google', 'Smartphone or tablet'),
13- ('generic', 'Generic HOTP/TOTP device')
14-]
15-
16-
17 def build_account_link(account):
18 url = reverse(
19 'admin:identityprovider_account_change', args=(account.id,))
20@@ -219,8 +210,6 @@
21 label="OATH HOTP/TOTP key")
22 name = forms.CharField(widget=forms.TextInput(attrs={'size': 15}),
23 label="Device name")
24- device_type = forms.ChoiceField(choices=DEVICE_TYPE_CHOICES,
25- label="Device type")
26
27 class Meta:
28 model = AuthenticationDevice
29@@ -231,6 +220,7 @@
30
31 model = AuthenticationDevice
32 form = AuthenticationDeviceInlineForm
33+ readonly_fields = ['device_type']
34 extra = 0
35
36
37
38=== modified file 'src/identityprovider/tests/test_admin.py'
39--- src/identityprovider/tests/test_admin.py 2016-09-05 18:11:55 +0000
40+++ src/identityprovider/tests/test_admin.py 2018-01-24 19:16:25 +0000
41@@ -237,7 +237,7 @@
42 key='some key',
43 name='Some device',
44 counter=124,
45- device_type=None,
46+ device_type='paper',
47 )
48 parameters = {
49 'devices-TOTAL_FORMS': '1',
50@@ -246,7 +246,7 @@
51 'devices-0-key': 'some key',
52 'devices-0-name': 'Some device',
53 'devices-0-counter': '123',
54- 'devices-0-device_type': 'paper',
55+ 'devices-0-device_type': 'foobar',
56 }
57 self.post_account_change(**parameters)
58
59@@ -254,7 +254,7 @@
60 self.assertEqual(device.counter, 123)
61 self.assertEqual(device.device_type, 'paper')
62
63- def get_device_inlineform(self, device_type):
64+ def test_device_inlineform(self):
65 device = AuthenticationDevice.objects.create(
66 account=self.account,
67 key='some key',
68@@ -266,14 +266,10 @@
69 'key': 'other key',
70 'name': 'A device',
71 'counter': '123',
72- 'device_type': device_type,
73+ 'device_type': 'foobar',
74 }
75 form = AuthenticationDeviceInlineForm(post_data, instance=device)
76- return form
77
78- def test_device_inlineform_invalid_type(self):
79- form = self.get_device_inlineform(device_type='foobar')
80- device = AuthenticationDevice.objects.all()[0]
81 expected_defaults = {
82 'id': device.id,
83 'key': 'some key',
84@@ -282,16 +278,7 @@
85 'device_type': None,
86 }
87 self.assertEqual(form.initial, expected_defaults)
88- self.assertEqual(form.is_valid(), False)
89-
90- msg = ('Select a valid choice. foobar is not one of the '
91- 'available choices.')
92- expected_errors = {'device_type': [msg]}
93- self.assertEqual(form.errors, expected_errors)
94-
95- def test_device_inlineform_valid_type(self):
96- form = self.get_device_inlineform(device_type='None')
97- self.assertEqual(form.is_valid(), True)
98+ self.assertTrue(form.is_valid())
99
100 def test_multiple_preferred_emails(self):
101 email = self.account.preferredemail