← Back to team overview

launchpad-reviewers team mailing list archive

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

 

William Grant has proposed merging lp:~wgrant/launchpad/destroy-person-password into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/destroy-person-password/+merge/88685

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.
-- 
https://code.launchpad.net/~wgrant/launchpad/destroy-person-password/+merge/88685
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/destroy-person-password into lp:launchpad.
=== modified file 'lib/lp/app/doc/launchpadform.txt'
--- lib/lp/app/doc/launchpadform.txt	2012-01-06 11:08:30 +0000
+++ lib/lp/app/doc/launchpadform.txt	2012-01-16 13:30:40 +0000
@@ -132,11 +132,9 @@
 
   >>> from zope.app.form.browser import TextWidget
   >>> from lp.app.browser.launchpadform import custom_widget
-  >>> from lp.app.widgets.password import PasswordChangeWidget
 
   >>> class FormTestView3(LaunchpadFormView):
   ...     schema = IFormTest
-  ...     custom_widget('password', PasswordChangeWidget)
   ...     custom_widget('displayname', TextWidget, displayWidth=50)
 
   >>> context = FormTest()
@@ -149,7 +147,7 @@
   >>> view.widgets['displayname'].displayWidth
   50
   >>> view.widgets['password']
-  <...PasswordChangeWidget object at ...>
+  <...TextWidget object at ...>
 
 
 == Using Another Context ==

=== removed file 'lib/lp/app/widgets/password.py'
--- lib/lp/app/widgets/password.py	2011-12-24 17:49:30 +0000
+++ lib/lp/app/widgets/password.py	1970-01-01 00:00:00 +0000
@@ -1,129 +0,0 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-"""
-Custom Password widgets.
-
-TODO: Consider folding this back into Zope3 -- StuartBishop 20050520
-"""
-
-__metaclass__ = type
-
-from z3c.ptcompat import ViewPageTemplateFile
-from zope.app.form.browser import PasswordWidget
-from zope.app.form.browser.interfaces import ITextBrowserWidget
-from zope.app.form.interfaces import WidgetInputError
-from zope.component import getUtility
-from zope.interface import implements
-from zope.schema.interfaces import ValidationError
-
-from lp import _
-from lp.services.webapp.interfaces import (
-    IMultiLineWidgetLayout,
-    IPasswordEncryptor,
-    )
-
-
-class PasswordMismatch(ValidationError):
-    __doc__ = _("Passwords do not match.")
-
-    def __repr__(self):
-        return repr(self.__doc__)
-
-
-class RequiredPasswordMissing(ValidationError):
-    __doc__ = _("You must enter a password.")
-
-    def __repr__(self):
-        return repr(self.__doc__)
-
-
-class PasswordChangeWidget(PasswordWidget):
-    """A password change widget.
-
-    Text is not echoed to the user, and two text boxes are used to ensure
-    the password is entered correctly.
-    """
-    implements(ITextBrowserWidget, IMultiLineWidgetLayout)
-    type = 'password change'
-    display_label = False
-
-    __call__ = ViewPageTemplateFile('templates/passwordchange.pt')
-
-    def hasInput(self):
-        """We always have input if there is an existing value
-
-        No input indicates unchanged.
-        """
-        if PasswordWidget.hasInput(self):
-            return True
-
-        # If we don't have input from the user, we lie because we will
-        # use the existing value.
-        return bool(self._getCurrentPassword())
-
-    def _getCurrentPassword(self):
-        # Yesh... indirection up the wazoo to do something this simple.
-        # Returns the current password.
-        return self.context.get(self.context.context) or None
-
-    def getInputValue(self):
-        """Ensure both text boxes contain the same value and inherited checks
-
-        >>> from lp.services.webapp.servers import (
-        ...     LaunchpadTestRequest)
-        >>> from zope.schema import Field
-        >>> field = Field(__name__='foo', title=u'Foo')
-
-        The widget will only return a value if both of the text boxes
-        contain the same value. It returns the value encrypted.
-
-        >>> request = LaunchpadTestRequest(form={
-        ...     'field.foo': u'My Password',
-        ...     'field.foo_dupe': u'My Password',
-        ...     })
-        >>> widget = PasswordChangeWidget(field, request)
-        >>> crypted_pw = widget.getInputValue()
-        >>> encryptor = getUtility(IPasswordEncryptor)
-        >>> encryptor.validate(u'My Password', crypted_pw)
-        True
-
-        Otherwise it raises the exception required by IInputWidget
-
-        >>> request = LaunchpadTestRequest(form={
-        ...     'field.foo': u'My Password', 'field.foo_dupe': u'No Match'})
-        >>> widget = PasswordChangeWidget(field, request)
-        >>> widget.getInputValue()
-        Traceback (most recent call last):
-            [...]
-        WidgetInputError: ('foo', u'Foo', u'Passwords do not match.')
-        """
-        value1 = self.request.form_ng.getOne(self.name, None)
-        value2 = self.request.form_ng.getOne('%s_dupe' % self.name, None)
-        if value1 != value2:
-            self._error = WidgetInputError(
-                    self.context.__name__, self.label, PasswordMismatch()
-                    )
-            raise self._error
-
-        # If the user hasn't entered a password, we use the existing one
-        # if it is there. If it is not there, we are creating a new password
-        # and we raise an error
-        if not value1:
-            current_password = self._getCurrentPassword()
-            if current_password:
-                return current_password
-            else:
-                self._error = WidgetInputError(
-                        self.context.__name__, self.label,
-                        RequiredPasswordMissing()
-                        )
-                raise self._error
-
-        # Do any other validation
-        value = PasswordWidget.getInputValue(self)
-        assert value == value1, 'Form system has changed'
-
-        # If we have matching plaintext, encrypt it and return the password
-        encryptor = getUtility(IPasswordEncryptor)
-        return encryptor.encrypt(value)

=== removed file 'lib/lp/app/widgets/templates/passwordchange.pt'
--- lib/lp/app/widgets/templates/passwordchange.pt	2009-07-28 22:44:12 +0000
+++ lib/lp/app/widgets/templates/passwordchange.pt	1970-01-01 00:00:00 +0000
@@ -1,35 +0,0 @@
-<tal:comment condition="nothing">
-    Note that we don't use view/_getFormValue to fill in the value
-    for security reasons
-</tal:comment>
-<table style="margin-top:1em;">
-  <tr>
-    <td colspan="2">
-      <label tal:content="string: ${view/label}:"
-             tal:attributes="for view/name;">Password:</label><br />
-      <input type="password" value="" tal:attributes="
-        name view/name;
-        id view/name;
-        class view/cssClass;
-        style view/style;
-        size view/displayWidth;
-        maxlength view/displayMaxWidth|nothing;
-        "/>
-    </td>
-  </tr>
-  <tr>
-    <td colspan="2">
-      <label tal:attributes="for string:${view/name}_dupe;">
-        Retype the password:
-      </label><br />
-      <input type="password" value="" tal:attributes="
-        name string:${view/name}_dupe;
-        id string:${view/name}_dupe;
-        class view/cssClass;
-        style view/style;
-        size view/displayWidth;
-        maxlength view/displayMaxWidth|nothing;
-        "/>
-    </td>
-  </tr>
-</table>

=== modified file 'lib/lp/app/widgets/tests/test_widget_doctests.py'
--- lib/lp/app/widgets/tests/test_widget_doctests.py	2011-12-28 17:03:06 +0000
+++ lib/lp/app/widgets/tests/test_widget_doctests.py	2012-01-16 13:30:40 +0000
@@ -12,7 +12,6 @@
 def test_suite():
     suite = unittest.TestSuite()
     suite.layer = DatabaseFunctionalLayer
-    suite.addTest(doctest.DocTestSuite('lp.app.widgets.password'))
     suite.addTest(doctest.DocTestSuite('lp.app.widgets.textwidgets'))
     suite.addTest(doctest.DocTestSuite('lp.app.widgets.date'))
     return suite

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2011-12-30 06:14:56 +0000
+++ lib/lp/registry/browser/person.py	2012-01-16 13:30:40 +0000
@@ -155,7 +155,6 @@
     LaunchpadRadioWidgetWithDescription,
     )
 from lp.app.widgets.location import LocationWidget
-from lp.app.widgets.password import PasswordChangeWidget
 from lp.blueprints.browser.specificationtarget import HasSpecificationsView
 from lp.blueprints.enums import SpecificationFilter
 from lp.bugs.browser.bugtask import BugTaskSearchListingView
@@ -971,8 +970,7 @@
 
     usedfor = IPersonEditMenu
     facet = 'overview'
-    links = ('personal', 'email_settings',
-             'sshkeys', 'gpgkeys', 'passwords')
+    links = ('personal', 'email_settings', 'sshkeys', 'gpgkeys')
 
     def personal(self):
         target = '+edit'
@@ -1277,7 +1275,6 @@
     label = "Review person's account"
     custom_widget(
         'status_comment', TextAreaWidget, height=5, width=60)
-    custom_widget('password', PasswordChangeWidget)
 
     def __init__(self, context, request):
         """See `LaunchpadEditFormView`."""
@@ -1290,7 +1287,7 @@
         # Set fields to be displayed.
         self.field_names = ['status', 'status_comment']
         if self.viewed_by_admin:
-            self.field_names = ['displayname', 'password'] + self.field_names
+            self.field_names = ['displayname'] + self.field_names
 
     @property
     def is_viewing_person(self):
@@ -1334,10 +1331,8 @@
         """Update the IAccount."""
         if (data['status'] == AccountStatus.SUSPENDED
             and self.context.status != AccountStatus.SUSPENDED):
-            # Setting the password to a clear value makes it impossible to
-            # login. The preferred email address is removed to ensure no
-            # email is sent to the user.
-            data['password'] = 'invalid'
+            # The preferred email address is removed to ensure no email
+            # is sent to the user.
             self.person.setPreferredEmail(None)
             self.request.response.addInfoNotification(
                 u'The account "%s" has been suspended.' % (

=== modified file 'lib/lp/registry/browser/tests/person-admin-views.txt'
--- lib/lp/registry/browser/tests/person-admin-views.txt	2011-12-24 17:49:30 +0000
+++ lib/lp/registry/browser/tests/person-admin-views.txt	2012-01-16 13:30:40 +0000
@@ -31,7 +31,6 @@
 The PersonAdministerView allows Launchpad admins to change some
 of a user's attributes.
 
-    >>> old_password = user.password
     >>> form = {
     ...     'field.name': 'zaphod',
     ...     'field.displayname': 'Zaphod Beeblebrox',
@@ -78,7 +77,7 @@
     >>> print view.errors
     []
     >>> view.field_names
-    ['displayname', 'password', 'status', 'status_comment']
+    ['displayname', 'status', 'status_comment']
     >>> view.label
     "Review person's account"
 
@@ -97,32 +96,25 @@
 
 The admin can change the user's account information.
 
-    >>> old_password = user.password
     >>> form = {
     ...     'field.displayname': 'The Zaphod Beeblebrox',
-    ...     'field.password': 'secret1',
-    ...     'field.password_dupe': 'secret1',
     ...     'field.status': 'ACTIVE',
     ...     'field.actions.change': 'Change',
     ...     }
     >>> view = create_initialized_view(user, '+reviewaccount', form=form)
     >>> print view.errors
     []
-    >>> user.password != old_password
-    True
     >>> print user.displayname
     Zaphod Beeblebrox
 
 An admin can suspend a user's account using the +reviewaccount view. When
-an account is suspended, the preferred email address is disabled and the
-password is changed too.
+an account is suspended, the preferred email address is disabled.
 
     >>> user.account_status
     <DBItem AccountStatus.ACTIVE, ...>
     >>> print user.account_status_comment
     None
 
-    >>> old_password = user.password
     >>> form = {
     ...     'field.displayname': 'Zaphod Beeblebrox',
     ...     'field.status': 'SUSPENDED',
@@ -137,16 +129,13 @@
     <DBItem AccountStatus.SUSPENDED, ...>
     >>> print user.account_status_comment
     Wanted by the galactic police.
-    >>> user.password != old_password
-    True
     >>> print user.preferredemail
     None
 
 An admin can reactivate a disabled user's account too. Unlike the act of
-suspension, reactivation does not change the user's password or email
-addresses; the user must use the reset password feature to restore these.
+suspension, reactivation does not change the user's email addresses; the
+user must log in to restore these.
 
-    >>> old_password = user.password
     >>> form = {
     ...     'field.displayname': 'Zaphod Beeblebrox',
     ...     'field.status': 'ACTIVE',
@@ -160,7 +149,5 @@
     <DBItem AccountStatus.ACTIVE, ...>
     >>> print user.account_status_comment
     Zaphod's a hoopy frood.
-    >>> user.password == old_password
-    True
     >>> print user.preferredemail
     None

=== modified file 'lib/lp/registry/doc/person-account.txt'
--- lib/lp/registry/doc/person-account.txt	2012-01-03 12:44:03 +0000
+++ lib/lp/registry/doc/person-account.txt	2012-01-16 13:30:40 +0000
@@ -12,7 +12,9 @@
 process. Matsubara's account was created during a code import.
 
     >>> from zope.component import getUtility
-    >>> from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
+    >>> from lp.services.identity.interfaces.emailaddress import (
+    ...     IEmailAddressSet,
+    ...     )
     >>> from lp.registry.interfaces.person import IPersonSet
 
     >>> emailset = getUtility(IEmailAddressSet)
@@ -22,8 +24,6 @@
     False
     >>> matsubara.account_status
     <DBItem AccountStatus.NOACCOUNT, ...>
-    >>> print matsubara.password
-    None
     >>> print matsubara.preferredemail
     None
 

=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py	2011-12-30 06:14:56 +0000
+++ lib/lp/registry/interfaces/person.py	2012-01-16 13:30:40 +0000
@@ -145,7 +145,6 @@
     is_public_person_or_closed_team,
     LogoImageUpload,
     MugshotImageUpload,
-    PasswordField,
     PersonChoice,
     PublicPersonChoice,
     StrippedTextLine,
@@ -766,8 +765,6 @@
     """IPerson attributes that require launchpad.View permission."""
     account = Object(schema=IAccount)
     accountID = Int(title=_('Account ID'), required=True, readonly=True)
-    password = PasswordField(
-        title=_('Password'), required=True, readonly=False)
     karma = exported(
         Int(title=_('Karma'), readonly=True,
             description=_('The cached total karma for this person.')))

=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py	2012-01-12 08:13:59 +0000
+++ lib/lp/registry/model/person.py	2012-01-16 13:30:40 +0000
@@ -260,10 +260,7 @@
     IEmailAddressSet,
     InvalidEmailAddress,
     )
-from lp.services.identity.model.account import (
-    Account,
-    AccountPassword,
-    )
+from lp.services.identity.model.account import Account
 from lp.services.identity.model.emailaddress import (
     EmailAddress,
     HasOwnerMixin,
@@ -528,24 +525,6 @@
     mugshot = ForeignKey(
         dbName='mugshot', foreignKey='LibraryFileAlias', default=None)
 
-    def _get_password(self):
-        # We have to remove the security proxy because the password is
-        # needed before we are authenticated. I'm not overly worried because
-        # this method is scheduled for demolition -- StuartBishop 20080514
-        password = IStore(AccountPassword).find(
-            AccountPassword, accountID=self.accountID).one()
-        if password is None:
-            return None
-        else:
-            return password.password
-
-    def _set_password(self, value):
-        account = IMasterStore(Account).get(Account, self.accountID)
-        assert account is not None, 'No account for this Person.'
-        account.password = value
-
-    password = property(_get_password, _set_password)
-
     def _get_account_status(self):
         account = IStore(Account).get(Account, self.accountID)
         if account is not None:

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-01-05 09:02:37 +0000
+++ lib/lp/testing/factory.py	2012-01-16 13:30:40 +0000
@@ -647,9 +647,6 @@
         if homepage_content is not None:
             naked_person.homepage_content = homepage_content
 
-        assert person.password is not None, (
-            'Password not set. Wrong default auth Store?')
-
         if (time_zone is not None or latitude is not None or
             longitude is not None):
             naked_person.setLocation(latitude, longitude, time_zone, person)