← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/registry-view-accounts into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/registry-view-accounts into lp:launchpad.

Commit message:
Let ~registry see OpenID identifiers, validated email addresses, and guessed email addresses on +reviewaccount.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/registry-view-accounts/+merge/143438

We sometimes need to see users' OpenID identifiers. They're shown on +reviewaccount, but only for admins. This branch lets ~registry see them too, as they're not sensitive.

I also added guessed (NEW) email addresses to the page, as they can be useful for working out account merges and strange OpenID linkage incidents.
-- 
https://code.launchpad.net/~wgrant/launchpad/registry-view-accounts/+merge/143438
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/registry-view-accounts into lp:launchpad.
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2013-01-11 03:41:17 +0000
+++ lib/lp/registry/browser/person.py	2013-01-16 05:13:23 +0000
@@ -1133,23 +1133,12 @@
     """Administer an `IPerson`."""
     schema = IPerson
     label = "Review person"
-    default_field_names = [
+    field_names = [
         'name', 'displayname',
         'personal_standing', 'personal_standing_reason']
     custom_widget(
         'personal_standing_reason', TextAreaWidget, height=5, width=60)
 
-    def setUpFields(self):
-        """Setup the normal fields from the schema, as appropriate.
-
-        If not an admin (e.g. registry expert), remove the displayname field.
-        """
-        self.field_names = self.default_field_names[:]
-        admin = check_permission('launchpad.Admin', self.context)
-        if not admin:
-            self.field_names.remove('displayname')
-        super(PersonAdministerView, self).setUpFields()
-
     @property
     def is_viewing_person(self):
         """Is the view showing an `IPerson`?
@@ -1179,6 +1168,7 @@
     """Administer an `IAccount` belonging to an `IPerson`."""
     schema = IAccount
     label = "Review person's account"
+    field_names = ['displayname', 'status', 'status_comment']
     custom_widget(
         'status_comment', TextAreaWidget, height=5, width=60)
 
@@ -1189,10 +1179,6 @@
         # It also means that permissions are checked on IAccount, not IPerson.
         self.person = self.context
         self.context = self.person.account
-        # Set fields to be displayed.
-        self.field_names = ['status', 'status_comment']
-        if self.viewed_by_admin:
-            self.field_names = ['displayname'] + self.field_names
 
     @property
     def is_viewing_person(self):
@@ -1204,11 +1190,6 @@
         return False
 
     @property
-    def viewed_by_admin(self):
-        """Is the user a Launchpad admin?"""
-        return check_permission('launchpad.Admin', self.context)
-
-    @property
     def email_addresses(self):
         """A list of the user's preferred and validated email addresses."""
         emails = sorted(
@@ -1218,12 +1199,17 @@
         return emails
 
     @property
+    def guessed_email_addresses(self):
+        """A list of the user's new email addresses.
+
+        This is just EmailAddressStatus.NEW addresses, not unvalidated ones
+        created through the web UI. They only have LoginTokens.
+        """
+        return sorted(email.email for email in self.person.guessedemails)
+
+    @property
     def next_url(self):
         """See `LaunchpadEditFormView`."""
-        is_suspended = self.context.status == AccountStatus.SUSPENDED
-        if is_suspended and not self.viewed_by_admin:
-            # Non-admins cannot see suspended persons.
-            return canonical_url(getUtility(IPersonSet))
         return canonical_url(self.person)
 
     @property

=== modified file 'lib/lp/registry/stories/person/xx-admin-person-review.txt'
--- lib/lp/registry/stories/person/xx-admin-person-review.txt	2012-12-10 13:43:47 +0000
+++ lib/lp/registry/stories/person/xx-admin-person-review.txt	2013-01-16 05:13:23 +0000
@@ -1,23 +1,49 @@
 Review person
 =============
 
-Launchpad admins such as Foo Bar can review users and update some of
-their information.
-
-    >>> admin_browser.open('http://launchpad.dev/~salgado')
-
-    >>> admin_browser.getLink('Administer').click()
-    >>> print admin_browser.url
+Registry admins can review users and update some of their information.
+
+    >>> email = "expert@xxxxxxxxxxx"
+    >>> expert = factory.makeRegistryExpert(email=email)
+    >>> expert_browser = setupBrowser(auth='Basic %s:test' % email)
+    >>> logout()
+
+    >>> expert_browser.open('http://launchpad.dev/~salgado')
+    >>> expert_browser.getLink('Administer').click()
+    >>> print expert_browser.url
     http://launchpad.dev/~salgado/+review
 
-    >>> print admin_browser.title
+    >>> print expert_browser.title
     Review person...
 
+    >>> expert_browser.getControl('Name', index=0).value = 'no-way'
+    >>> expert_browser.getControl('Personal standing').value = ['GOOD']
+    >>> expert_browser.getControl(
+    ...     name='field.personal_standing_reason').value = 'good guy'
+    >>> expert_browser.getControl('Change').click()
+    >>> print expert_browser.url
+    http://launchpad.dev/~no-way
+
+Registry experts can't change the displayname.
+
+    >>> expert_browser.getLink('Administer').click()
+    >>> expert_browser.getControl(
+    ...     'Display Name', index=0).value = 'The one and only Salgado'
+    >>> expert_browser.getControl('Change').click()
+    Traceback (most recent call last):
+    ...
+    Unauthorized: ...
+
+But Launchpad admins can.
+    >>> admin_browser.open('http://launchpad.dev/~no-way/+review')
+    >>> admin_browser.getControl('Name', index=0).value = 'salgado'
     >>> admin_browser.getControl(
     ...     'Display Name', index=0).value = 'The one and only Salgado'
     >>> admin_browser.getControl('Change').click()
     >>> print admin_browser.title
     The one and only Salgado in Launchpad
+    >>> print admin_browser.url
+    http://launchpad.dev/~salgado
 
 
 Review account
@@ -25,84 +51,25 @@
 
 The review page has a link to the review account page.
 
-    >>> admin_browser.open('http://launchpad.dev/~salgado')
-    >>> admin_browser.getLink('Administer Account').click()
-    >>> print admin_browser.title
+    >>> expert_browser.open('http://launchpad.dev/~salgado')
+    >>> expert_browser.getLink('Administer Account').click()
+    >>> print expert_browser.title
     Review person's account...
 
 The +reviewaccount page displays account information that is normally
 hidden from the UI.
 
-    >>> content = find_main_content(admin_browser.contents)
+    >>> content = find_main_content(expert_browser.contents)
     >>> for tr in content.find(id='summary').findAll('tr'):
     ...     print extract_text(tr)
     Created: 2005-06-06
     Creation reason: Created by the owner himself, coming from Launchpad.
-    OpenID Identifiers: salgado_oid
-    Email Addresses: guilherme.salgado@xxxxxxxxxxxxx
+    OpenID identifiers: salgado_oid
+    Email addresses: guilherme.salgado@xxxxxxxxxxxxx
+    Guessed email addresses: salgado@xxxxxxxxxx
 
 The page also contains a link back to the +review page.
 
-    >>> link = admin_browser.getLink(url='+review')
+    >>> link = expert_browser.getLink(url='+review')
     >>> print link.text
     edit[IMG] Review the user's Launchpad information
-
-
-Registry experts
-----------------
-
-Registry experts can see the +review page, minus the displayname.
-
-    >>> email = "expert@xxxxxxxxxxx"
-    >>> expert = factory.makeRegistryExpert(email=email)
-    >>> expert_browser = setupBrowser(auth='Basic %s:test' % email)
-    >>> logout()
-    >>> expert_browser.open('http://launchpad.dev/~no-priv/+review')
-    >>> print expert_browser.title
-    Review person...
-
-    >>> expert_browser.getControl('Name', index=0).value = "no-way"
-    >>> expert_browser.getControl(
-    ...     'Display Name', index=0)
-    Traceback (most recent call last):
-    ...
-    LookupError: label 'Display Name'
-
-    >>> expert_browser.getControl('Personal standing').value = ['GOOD']
-    >>> expert_browser.getControl(
-    ...     name='field.personal_standing_reason').value = 'good guy'
-    >>> expert_browser.getControl('Change').click()
-    >>> print expert_browser.url
-    http://launchpad.dev/~no-way
-
-However, members of the registry team only get partial access to the
-review account page to be able to suspend spam accounts.
-
-    >>> expert_browser.open('http://launchpad.dev/~no-way/+reviewaccount')
-    >>> print expert_browser.title
-    Review person's account...
-
-The +reviewaccount page does not display account information for registry
-experts. It also has no form elements to change the display name.
-
-    >>> content = find_main_content(expert_browser.contents)
-    >>> print content.find(id='summary')
-    None
-    >>> print content.find(name='field.displayname')
-    None
-
-The only option for registry experts is to change an account's status and to
-provide a reason why they did it. After suspending the account the
-page will only be visible to admins, so the registry expert will be
-redirected to the "people" main page. A feedback message informs the expert
-about the suspension.
-
-    >>> control = expert_browser.getControl(name='field.status_comment')
-    >>> control.value = 'This is SPAM!'
-    >>> expert_browser.getControl(
-    ...     'The status of this account').value = ['SUSPENDED']
-    >>> expert_browser.getControl('Change').click()
-    >>> print expert_browser.url
-    http://launchpad.dev/people
-    >>> print get_feedback_messages(expert_browser.contents)[0]
-    The account "No Privileges Person" has been suspended.

=== modified file 'lib/lp/registry/templates/person-review.pt'
--- lib/lp/registry/templates/person-review.pt	2012-02-17 15:31:38 +0000
+++ lib/lp/registry/templates/person-review.pt	2013-01-16 05:13:23 +0000
@@ -27,8 +27,7 @@
             </p>
           </tal:review-person>
 
-          <tal:review-account
-               condition="python: not view.is_viewing_person and view.viewed_by_admin">
+          <tal:review-account condition="python: not view.is_viewing_person">
             <p>
               The account displayname is not always the same as the Launchpad
               displayname.
@@ -49,7 +48,7 @@
                 <td><tal:reason replace="view/context/creation_rationale/title" /></td>
               </tr>
               <tr>
-                <th>OpenID Identifiers:</th>
+                <th>OpenID identifiers:</th>
                 <td>
                   <ul>
                     <li tal:repeat="identifier view/context/openid_identifiers"
@@ -58,7 +57,7 @@
                 </td>
               </tr>
               <tr>
-                <th>Email Addresses:</th>
+                <th>Email addresses:</th>
                 <td>
                   <tal:emails repeat="email view/email_addresses">
                     <span style="white-space: nowrap">
@@ -68,6 +67,17 @@
                   </tal:emails>
                 </td>
               </tr>
+              <tr>
+                <th>Guessed email addresses:</th>
+                <td>
+                  <tal:emails repeat="email view/guessed_email_addresses">
+                    <span style="white-space: nowrap">
+                      <tal:email replace="email">foo2@xxxxxxx</tal:email>
+                      <br tal:omit-tag="repeat/email/end" />
+                    </span>
+                  </tal:emails>
+                </td>
+              </tr>
             </table>
           </tal:review-account>
         </div>

=== modified file 'lib/lp/security.py'
--- lib/lp/security.py	2013-01-09 05:37:18 +0000
+++ lib/lp/security.py	2013-01-16 05:13:23 +0000
@@ -382,6 +382,20 @@
 class ViewAccount(EditAccountBySelfOrAdmin):
     permission = 'launchpad.View'
 
+    def checkAuthenticated(self, user):
+        """Extend permission to registry experts."""
+        return (
+            super(ViewAccount, self).checkAuthenticated(user)
+            or user.in_registry_experts)
+
+
+class ModerateAccountByRegistryExpert(AuthorizationBase):
+    usedfor = IAccount
+    permission = 'launchpad.Moderate'
+
+    def checkAuthenticated(self, user):
+        return user.in_admin or user.in_registry_experts
+
 
 class ViewOpenIdIdentifierBySelfOrAdmin(AuthorizationBase):
     permission = 'launchpad.View'
@@ -391,24 +405,6 @@
         return user.in_admin or user.person.accountID == self.obj.accountID
 
 
-class SpecialAccount(EditAccountBySelfOrAdmin):
-    permission = 'launchpad.Special'
-
-    def checkAuthenticated(self, user):
-        """Extend permission to registry experts."""
-        return (
-            super(SpecialAccount, self).checkAuthenticated(user)
-            or user.in_registry_experts)
-
-
-class ModerateAccountByRegistryExpert(AuthorizationBase):
-    usedfor = IAccount
-    permission = 'launchpad.Moderate'
-
-    def checkAuthenticated(self, user):
-        return user.in_admin or user.in_registry_experts
-
-
 class EditOAuthAccessToken(AuthorizationBase):
     permission = 'launchpad.Edit'
     usedfor = IOAuthAccessToken

=== modified file 'lib/lp/services/identity/configure.zcml'
--- lib/lp/services/identity/configure.zcml	2012-01-15 04:50:33 +0000
+++ lib/lp/services/identity/configure.zcml	2013-01-16 05:13:23 +0000
@@ -50,10 +50,7 @@
             interface="lp.services.identity.interfaces.account.IAccountPublic"/>
         <require
             permission="launchpad.View"
-            interface="lp.services.identity.interfaces.account.IAccountPrivate"/>
-        <require
-            permission="launchpad.Special"
-            interface="lp.services.identity.interfaces.account.IAccountSpecialRestricted"/>
+            interface="lp.services.identity.interfaces.account.IAccountViewRestricted"/>
         <require
             permission="launchpad.Moderate"
             set_attributes="status date_status_set status_comment"/>

=== modified file 'lib/lp/services/identity/interfaces/account.py'
--- lib/lp/services/identity/interfaces/account.py	2013-01-07 02:40:55 +0000
+++ lib/lp/services/identity/interfaces/account.py	2013-01-16 05:13:23 +0000
@@ -11,10 +11,10 @@
     'AccountSuspendedError',
     'AccountCreationRationale',
     'IAccount',
-    'IAccountPrivate',
     'IAccountPublic',
     'IAccountSet',
     'IAccountSpecialRestricted',
+    'IAccountViewRestricted',
     'INACTIVE_ACCOUNT_STATUSES',
     ]
 
@@ -244,6 +244,7 @@
 
 class IAccountPublic(Interface):
     """Public information on an `IAccount`."""
+
     id = Int(title=_('ID'), required=True, readonly=True)
 
     displayname = StrippedTextLine(
@@ -255,8 +256,9 @@
         readonly=False, vocabulary=AccountStatus)
 
 
-class IAccountPrivate(Interface):
+class IAccountViewRestricted(Interface):
     """Private information on an `IAccount`."""
+
     date_created = Datetime(
         title=_('Date Created'), required=True, readonly=True)
 
@@ -266,10 +268,6 @@
 
     openid_identifiers = Attribute(_("Linked OpenId Identifiers"))
 
-
-class IAccountSpecialRestricted(Interface):
-    """Attributes of `IAccount` protected with launchpad.Special."""
-
     date_status_set = Datetime(
         title=_('Date status last modified.'),
         required=True, readonly=False)
@@ -287,7 +285,7 @@
         """
 
 
-class IAccount(IAccountPublic, IAccountPrivate, IAccountSpecialRestricted):
+class IAccount(IAccountPublic, IAccountViewRestricted):
     """Interface describing an `Account`."""