Merge lp:~wgrant/launchpad/destroy-person-password into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 14679
Proposed branch: lp:~wgrant/launchpad/destroy-person-password
Merge into: lp:launchpad
Diff against target: 467 lines (+14/-229)
11 files modified
lib/lp/app/doc/launchpadform.txt (+1/-3)
lib/lp/app/widgets/password.py (+0/-129)
lib/lp/app/widgets/templates/passwordchange.pt (+0/-35)
lib/lp/app/widgets/tests/test_widget_doctests.py (+0/-1)
lib/lp/registry/browser/person.py (+4/-9)
lib/lp/registry/browser/tests/person-admin-views.txt (+4/-17)
lib/lp/registry/doc/person-account.txt (+3/-3)
lib/lp/registry/doc/person.txt (+1/-4)
lib/lp/registry/interfaces/person.py (+0/-3)
lib/lp/registry/model/person.py (+1/-22)
lib/lp/testing/factory.py (+0/-3)
To merge this branch: bzr merge lp:~wgrant/launchpad/destroy-person-password
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code. Approve
Review via email: mp+88685@code.launchpad.net

Commit message

Remove admin password reset support. Launchpad doesn't manage passwords.

Description of the change

Launchpad hasn't managed passwords since April 2010, but the admin password reset form has remained until now. This branch kills it and some related stuff.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you very much for this branch. This fixes a few bugs. I will link the ones that I find.

review: Approve (code.)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/doc/launchpadform.txt'
2--- lib/lp/app/doc/launchpadform.txt 2012-01-06 11:08:30 +0000
3+++ lib/lp/app/doc/launchpadform.txt 2012-01-17 00:44:26 +0000
4@@ -132,11 +132,9 @@
5
6 >>> from zope.app.form.browser import TextWidget
7 >>> from lp.app.browser.launchpadform import custom_widget
8- >>> from lp.app.widgets.password import PasswordChangeWidget
9
10 >>> class FormTestView3(LaunchpadFormView):
11 ... schema = IFormTest
12- ... custom_widget('password', PasswordChangeWidget)
13 ... custom_widget('displayname', TextWidget, displayWidth=50)
14
15 >>> context = FormTest()
16@@ -149,7 +147,7 @@
17 >>> view.widgets['displayname'].displayWidth
18 50
19 >>> view.widgets['password']
20- <...PasswordChangeWidget object at ...>
21+ <...TextWidget object at ...>
22
23
24 == Using Another Context ==
25
26=== removed file 'lib/lp/app/widgets/password.py'
27--- lib/lp/app/widgets/password.py 2011-12-24 17:49:30 +0000
28+++ lib/lp/app/widgets/password.py 1970-01-01 00:00:00 +0000
29@@ -1,129 +0,0 @@
30-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
31-# GNU Affero General Public License version 3 (see the file LICENSE).
32-
33-"""
34-Custom Password widgets.
35-
36-TODO: Consider folding this back into Zope3 -- StuartBishop 20050520
37-"""
38-
39-__metaclass__ = type
40-
41-from z3c.ptcompat import ViewPageTemplateFile
42-from zope.app.form.browser import PasswordWidget
43-from zope.app.form.browser.interfaces import ITextBrowserWidget
44-from zope.app.form.interfaces import WidgetInputError
45-from zope.component import getUtility
46-from zope.interface import implements
47-from zope.schema.interfaces import ValidationError
48-
49-from lp import _
50-from lp.services.webapp.interfaces import (
51- IMultiLineWidgetLayout,
52- IPasswordEncryptor,
53- )
54-
55-
56-class PasswordMismatch(ValidationError):
57- __doc__ = _("Passwords do not match.")
58-
59- def __repr__(self):
60- return repr(self.__doc__)
61-
62-
63-class RequiredPasswordMissing(ValidationError):
64- __doc__ = _("You must enter a password.")
65-
66- def __repr__(self):
67- return repr(self.__doc__)
68-
69-
70-class PasswordChangeWidget(PasswordWidget):
71- """A password change widget.
72-
73- Text is not echoed to the user, and two text boxes are used to ensure
74- the password is entered correctly.
75- """
76- implements(ITextBrowserWidget, IMultiLineWidgetLayout)
77- type = 'password change'
78- display_label = False
79-
80- __call__ = ViewPageTemplateFile('templates/passwordchange.pt')
81-
82- def hasInput(self):
83- """We always have input if there is an existing value
84-
85- No input indicates unchanged.
86- """
87- if PasswordWidget.hasInput(self):
88- return True
89-
90- # If we don't have input from the user, we lie because we will
91- # use the existing value.
92- return bool(self._getCurrentPassword())
93-
94- def _getCurrentPassword(self):
95- # Yesh... indirection up the wazoo to do something this simple.
96- # Returns the current password.
97- return self.context.get(self.context.context) or None
98-
99- def getInputValue(self):
100- """Ensure both text boxes contain the same value and inherited checks
101-
102- >>> from lp.services.webapp.servers import (
103- ... LaunchpadTestRequest)
104- >>> from zope.schema import Field
105- >>> field = Field(__name__='foo', title=u'Foo')
106-
107- The widget will only return a value if both of the text boxes
108- contain the same value. It returns the value encrypted.
109-
110- >>> request = LaunchpadTestRequest(form={
111- ... 'field.foo': u'My Password',
112- ... 'field.foo_dupe': u'My Password',
113- ... })
114- >>> widget = PasswordChangeWidget(field, request)
115- >>> crypted_pw = widget.getInputValue()
116- >>> encryptor = getUtility(IPasswordEncryptor)
117- >>> encryptor.validate(u'My Password', crypted_pw)
118- True
119-
120- Otherwise it raises the exception required by IInputWidget
121-
122- >>> request = LaunchpadTestRequest(form={
123- ... 'field.foo': u'My Password', 'field.foo_dupe': u'No Match'})
124- >>> widget = PasswordChangeWidget(field, request)
125- >>> widget.getInputValue()
126- Traceback (most recent call last):
127- [...]
128- WidgetInputError: ('foo', u'Foo', u'Passwords do not match.')
129- """
130- value1 = self.request.form_ng.getOne(self.name, None)
131- value2 = self.request.form_ng.getOne('%s_dupe' % self.name, None)
132- if value1 != value2:
133- self._error = WidgetInputError(
134- self.context.__name__, self.label, PasswordMismatch()
135- )
136- raise self._error
137-
138- # If the user hasn't entered a password, we use the existing one
139- # if it is there. If it is not there, we are creating a new password
140- # and we raise an error
141- if not value1:
142- current_password = self._getCurrentPassword()
143- if current_password:
144- return current_password
145- else:
146- self._error = WidgetInputError(
147- self.context.__name__, self.label,
148- RequiredPasswordMissing()
149- )
150- raise self._error
151-
152- # Do any other validation
153- value = PasswordWidget.getInputValue(self)
154- assert value == value1, 'Form system has changed'
155-
156- # If we have matching plaintext, encrypt it and return the password
157- encryptor = getUtility(IPasswordEncryptor)
158- return encryptor.encrypt(value)
159
160=== removed file 'lib/lp/app/widgets/templates/passwordchange.pt'
161--- lib/lp/app/widgets/templates/passwordchange.pt 2009-07-28 22:44:12 +0000
162+++ lib/lp/app/widgets/templates/passwordchange.pt 1970-01-01 00:00:00 +0000
163@@ -1,35 +0,0 @@
164-<tal:comment condition="nothing">
165- Note that we don't use view/_getFormValue to fill in the value
166- for security reasons
167-</tal:comment>
168-<table style="margin-top:1em;">
169- <tr>
170- <td colspan="2">
171- <label tal:content="string: ${view/label}:"
172- tal:attributes="for view/name;">Password:</label><br />
173- <input type="password" value="" tal:attributes="
174- name view/name;
175- id view/name;
176- class view/cssClass;
177- style view/style;
178- size view/displayWidth;
179- maxlength view/displayMaxWidth|nothing;
180- "/>
181- </td>
182- </tr>
183- <tr>
184- <td colspan="2">
185- <label tal:attributes="for string:${view/name}_dupe;">
186- Retype the password:
187- </label><br />
188- <input type="password" value="" tal:attributes="
189- name string:${view/name}_dupe;
190- id string:${view/name}_dupe;
191- class view/cssClass;
192- style view/style;
193- size view/displayWidth;
194- maxlength view/displayMaxWidth|nothing;
195- "/>
196- </td>
197- </tr>
198-</table>
199
200=== modified file 'lib/lp/app/widgets/tests/test_widget_doctests.py'
201--- lib/lp/app/widgets/tests/test_widget_doctests.py 2011-12-28 17:03:06 +0000
202+++ lib/lp/app/widgets/tests/test_widget_doctests.py 2012-01-17 00:44:26 +0000
203@@ -12,7 +12,6 @@
204 def test_suite():
205 suite = unittest.TestSuite()
206 suite.layer = DatabaseFunctionalLayer
207- suite.addTest(doctest.DocTestSuite('lp.app.widgets.password'))
208 suite.addTest(doctest.DocTestSuite('lp.app.widgets.textwidgets'))
209 suite.addTest(doctest.DocTestSuite('lp.app.widgets.date'))
210 return suite
211
212=== modified file 'lib/lp/registry/browser/person.py'
213--- lib/lp/registry/browser/person.py 2011-12-30 06:14:56 +0000
214+++ lib/lp/registry/browser/person.py 2012-01-17 00:44:26 +0000
215@@ -155,7 +155,6 @@
216 LaunchpadRadioWidgetWithDescription,
217 )
218 from lp.app.widgets.location import LocationWidget
219-from lp.app.widgets.password import PasswordChangeWidget
220 from lp.blueprints.browser.specificationtarget import HasSpecificationsView
221 from lp.blueprints.enums import SpecificationFilter
222 from lp.bugs.browser.bugtask import BugTaskSearchListingView
223@@ -971,8 +970,7 @@
224
225 usedfor = IPersonEditMenu
226 facet = 'overview'
227- links = ('personal', 'email_settings',
228- 'sshkeys', 'gpgkeys', 'passwords')
229+ links = ('personal', 'email_settings', 'sshkeys', 'gpgkeys')
230
231 def personal(self):
232 target = '+edit'
233@@ -1277,7 +1275,6 @@
234 label = "Review person's account"
235 custom_widget(
236 'status_comment', TextAreaWidget, height=5, width=60)
237- custom_widget('password', PasswordChangeWidget)
238
239 def __init__(self, context, request):
240 """See `LaunchpadEditFormView`."""
241@@ -1290,7 +1287,7 @@
242 # Set fields to be displayed.
243 self.field_names = ['status', 'status_comment']
244 if self.viewed_by_admin:
245- self.field_names = ['displayname', 'password'] + self.field_names
246+ self.field_names = ['displayname'] + self.field_names
247
248 @property
249 def is_viewing_person(self):
250@@ -1334,10 +1331,8 @@
251 """Update the IAccount."""
252 if (data['status'] == AccountStatus.SUSPENDED
253 and self.context.status != AccountStatus.SUSPENDED):
254- # Setting the password to a clear value makes it impossible to
255- # login. The preferred email address is removed to ensure no
256- # email is sent to the user.
257- data['password'] = 'invalid'
258+ # The preferred email address is removed to ensure no email
259+ # is sent to the user.
260 self.person.setPreferredEmail(None)
261 self.request.response.addInfoNotification(
262 u'The account "%s" has been suspended.' % (
263
264=== modified file 'lib/lp/registry/browser/tests/person-admin-views.txt'
265--- lib/lp/registry/browser/tests/person-admin-views.txt 2011-12-24 17:49:30 +0000
266+++ lib/lp/registry/browser/tests/person-admin-views.txt 2012-01-17 00:44:26 +0000
267@@ -31,7 +31,6 @@
268 The PersonAdministerView allows Launchpad admins to change some
269 of a user's attributes.
270
271- >>> old_password = user.password
272 >>> form = {
273 ... 'field.name': 'zaphod',
274 ... 'field.displayname': 'Zaphod Beeblebrox',
275@@ -78,7 +77,7 @@
276 >>> print view.errors
277 []
278 >>> view.field_names
279- ['displayname', 'password', 'status', 'status_comment']
280+ ['displayname', 'status', 'status_comment']
281 >>> view.label
282 "Review person's account"
283
284@@ -97,32 +96,25 @@
285
286 The admin can change the user's account information.
287
288- >>> old_password = user.password
289 >>> form = {
290 ... 'field.displayname': 'The Zaphod Beeblebrox',
291- ... 'field.password': 'secret1',
292- ... 'field.password_dupe': 'secret1',
293 ... 'field.status': 'ACTIVE',
294 ... 'field.actions.change': 'Change',
295 ... }
296 >>> view = create_initialized_view(user, '+reviewaccount', form=form)
297 >>> print view.errors
298 []
299- >>> user.password != old_password
300- True
301 >>> print user.displayname
302 Zaphod Beeblebrox
303
304 An admin can suspend a user's account using the +reviewaccount view. When
305-an account is suspended, the preferred email address is disabled and the
306-password is changed too.
307+an account is suspended, the preferred email address is disabled.
308
309 >>> user.account_status
310 <DBItem AccountStatus.ACTIVE, ...>
311 >>> print user.account_status_comment
312 None
313
314- >>> old_password = user.password
315 >>> form = {
316 ... 'field.displayname': 'Zaphod Beeblebrox',
317 ... 'field.status': 'SUSPENDED',
318@@ -137,16 +129,13 @@
319 <DBItem AccountStatus.SUSPENDED, ...>
320 >>> print user.account_status_comment
321 Wanted by the galactic police.
322- >>> user.password != old_password
323- True
324 >>> print user.preferredemail
325 None
326
327 An admin can reactivate a disabled user's account too. Unlike the act of
328-suspension, reactivation does not change the user's password or email
329-addresses; the user must use the reset password feature to restore these.
330+suspension, reactivation does not change the user's email addresses; the
331+user must log in to restore these.
332
333- >>> old_password = user.password
334 >>> form = {
335 ... 'field.displayname': 'Zaphod Beeblebrox',
336 ... 'field.status': 'ACTIVE',
337@@ -160,7 +149,5 @@
338 <DBItem AccountStatus.ACTIVE, ...>
339 >>> print user.account_status_comment
340 Zaphod's a hoopy frood.
341- >>> user.password == old_password
342- True
343 >>> print user.preferredemail
344 None
345
346=== modified file 'lib/lp/registry/doc/person-account.txt'
347--- lib/lp/registry/doc/person-account.txt 2012-01-03 12:44:03 +0000
348+++ lib/lp/registry/doc/person-account.txt 2012-01-17 00:44:26 +0000
349@@ -12,7 +12,9 @@
350 process. Matsubara's account was created during a code import.
351
352 >>> from zope.component import getUtility
353- >>> from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
354+ >>> from lp.services.identity.interfaces.emailaddress import (
355+ ... IEmailAddressSet,
356+ ... )
357 >>> from lp.registry.interfaces.person import IPersonSet
358
359 >>> emailset = getUtility(IEmailAddressSet)
360@@ -22,8 +24,6 @@
361 False
362 >>> matsubara.account_status
363 <DBItem AccountStatus.NOACCOUNT, ...>
364- >>> print matsubara.password
365- None
366 >>> print matsubara.preferredemail
367 None
368
369
370=== modified file 'lib/lp/registry/doc/person.txt'
371--- lib/lp/registry/doc/person.txt 2012-01-05 00:23:45 +0000
372+++ lib/lp/registry/doc/person.txt 2012-01-17 00:44:26 +0000
373@@ -142,7 +142,7 @@
374 >>> from lp.services.identity.model.account import Account
375 >>> from lp.services.database.lpstorm import IMasterStore
376 >>> account = IMasterStore(Account).get(Account, p.accountID)
377- >>> account.reactivate("Activated by doc test.", password=p.password)
378+ >>> account.reactivate("Activated by doc test.", account.password)
379 >>> p.account_status
380 <DBItem AccountStatus.ACTIVE...
381
382@@ -485,9 +485,6 @@
383 >>> verifyObject(ITeam, not_a_person)
384 True
385
386- >>> print removeSecurityProxy(not_a_person).password
387- None
388-
389 The team owner is also added as an administrator of its team.
390
391 >>> [member.name for member in not_a_person.adminmembers]
392
393=== modified file 'lib/lp/registry/interfaces/person.py'
394--- lib/lp/registry/interfaces/person.py 2011-12-30 06:14:56 +0000
395+++ lib/lp/registry/interfaces/person.py 2012-01-17 00:44:26 +0000
396@@ -145,7 +145,6 @@
397 is_public_person_or_closed_team,
398 LogoImageUpload,
399 MugshotImageUpload,
400- PasswordField,
401 PersonChoice,
402 PublicPersonChoice,
403 StrippedTextLine,
404@@ -766,8 +765,6 @@
405 """IPerson attributes that require launchpad.View permission."""
406 account = Object(schema=IAccount)
407 accountID = Int(title=_('Account ID'), required=True, readonly=True)
408- password = PasswordField(
409- title=_('Password'), required=True, readonly=False)
410 karma = exported(
411 Int(title=_('Karma'), readonly=True,
412 description=_('The cached total karma for this person.')))
413
414=== modified file 'lib/lp/registry/model/person.py'
415--- lib/lp/registry/model/person.py 2012-01-12 08:13:59 +0000
416+++ lib/lp/registry/model/person.py 2012-01-17 00:44:26 +0000
417@@ -260,10 +260,7 @@
418 IEmailAddressSet,
419 InvalidEmailAddress,
420 )
421-from lp.services.identity.model.account import (
422- Account,
423- AccountPassword,
424- )
425+from lp.services.identity.model.account import Account
426 from lp.services.identity.model.emailaddress import (
427 EmailAddress,
428 HasOwnerMixin,
429@@ -528,24 +525,6 @@
430 mugshot = ForeignKey(
431 dbName='mugshot', foreignKey='LibraryFileAlias', default=None)
432
433- def _get_password(self):
434- # We have to remove the security proxy because the password is
435- # needed before we are authenticated. I'm not overly worried because
436- # this method is scheduled for demolition -- StuartBishop 20080514
437- password = IStore(AccountPassword).find(
438- AccountPassword, accountID=self.accountID).one()
439- if password is None:
440- return None
441- else:
442- return password.password
443-
444- def _set_password(self, value):
445- account = IMasterStore(Account).get(Account, self.accountID)
446- assert account is not None, 'No account for this Person.'
447- account.password = value
448-
449- password = property(_get_password, _set_password)
450-
451 def _get_account_status(self):
452 account = IStore(Account).get(Account, self.accountID)
453 if account is not None:
454
455=== modified file 'lib/lp/testing/factory.py'
456--- lib/lp/testing/factory.py 2012-01-05 09:02:37 +0000
457+++ lib/lp/testing/factory.py 2012-01-17 00:44:26 +0000
458@@ -647,9 +647,6 @@
459 if homepage_content is not None:
460 naked_person.homepage_content = homepage_content
461
462- assert person.password is not None, (
463- 'Password not set. Wrong default auth Store?')
464-
465 if (time_zone is not None or latitude is not None or
466 longitude is not None):
467 naked_person.setLocation(latitude, longitude, time_zone, person)