Merge lp:~sinzui/launchpad/delete-team-4 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: 12052
Proposed branch: lp:~sinzui/launchpad/delete-team-4
Merge into: lp:launchpad
Diff against target: 103 lines (+26/-9)
2 files modified
lib/lp/registry/browser/peoplemerge.py (+12/-7)
lib/lp/registry/browser/tests/test_peoplemerge.py (+14/-2)
To merge this branch: bzr merge lp:~sinzui/launchpad/delete-team-4
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Approve
Review via email: mp+43403@code.launchpad.net

Description of the change

Allow registry admin to delete teams with super teams.

    Launchpad bug: https://bugs.launchpad.net/bugs/687676
    Pre-implementation: no one
    Test command: ./bin/test -vv -t TestAdminTeamMergeView

The recent change to remove super teams before starting a merge breaks team
deletions by the registry admins. The team does not have permission to call
retractTeamMembership in many cases. There are two issues underlying this
symptom.

The loop is iterating over all teams participated in, not the direct
membership. retractTeamMembership() is safe to call in this case when
the user has permission. Even when the user has permission to call the
method on the direct team, there is a failure on the indirect team.

As with view code that prepares an object for deletion or merge, the case
requires super privileges to complete the cleanup. Either Registry admins
should have launchpad.Edit on the team, or gets special privileges on the
method, or the view removed the security proxy to complete the task.

--------------------------------------------------------------------

RULES

    * Person does not provide a method to get direct super teams, but that
      can be derived in an extra db call to get the indirect teams.
    * Use removeSecurityProxy() to permit registry admins to complete
      the implicit tasks implied by the delete action. This is already
      done when working with the team email addresses. It can be done
      when calling retractTeamMembership()

QA

    * As a registry admin, delete these teams:
      https://launchpad.net/people/?name=delete&searchfor=teamsonly

LINT

    lib/lp/registry/browser/peoplemerge.py
    lib/lp/registry/browser/tests/test_peoplemerge.py

IMPLEMENTATION

The set solution to get the direct team membership is easy, but I think it
odd that my search of the code did not find other cases where these teams
are needed. I decided not to create a method on IPerson since there would
be only one call site. Added a test that duplicated what I experienced in
production, then shut my eye and imported removeSecurityProxy.
    lib/lp/registry/browser/peoplemerge.py
    lib/lp/registry/browser/tests/test_peoplemerge.py

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

I think adding a method would be better for several reasons:
 - it would allow using APIs to manage teams (e.g. for
sysadmins/registry experts)
 - the retractTeamMembership calls should be in model code anyway.

Revision history for this message
Curtis Hovey (sinzui) wrote :

Previous conversations concluded registry admins do not have permission to manage membership.

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Dec 13, 2010 at 8:09 AM, Curtis Hovey
<email address hidden> wrote:
> Previous conversations concluded registry admins do not have permission to manage membership.

Well, sysadmins certainly do, and scripts can be useful. It will also
make for better layers and more efficient tests if all the domain code
is in model/.

-Rob

Revision history for this message
Curtis Hovey (sinzui) wrote :

Admins and team owner can use a script to remove super team now. The issues is that admins takes days to do this. Helping users manage their data is not the admin'a primary responsibility. Owners often need assistance, or in cases where teams are reorganising themselves, there is not user as an owner who can do this.

This really is the domain of registry admins, but Francis has stated he does not want this power to be in the hands of many people, even if they are trusted. Launchpad engineers are too many to be trusted. We instead use the UI to help registry admins do they job. This is such as case. deletion is an invasive action that will always involve access to other object that the user does not have access too. The UI recognises that the user's right to delete a team, release, milestone, or series it supersedes the right of the owner of the linked object to have such as link.

This issue is a regression. I had power to help users, now I do not. We have conflicts of interest in the concepts of admin, moderate, and edit. I am a moderator, the team owner is an editor. and we do not have a permission that permits us to state that both roles can make the change. And to repeat my earlier remark, the conclusion is that I should not have the power retract memberships, only the power the delete the team.

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Dec 13, 2010 at 9:17 AM, Curtis Hovey
<email address hidden> wrote:
> This issue is a regression. I had power to help users, now I do not. We have conflicts of interest in the concepts of admin, moderate, and edit. I am a moderator, the team owner is an editor. and we do not have a permission that permits us to state that both roles can make the change. And to repeat my earlier remark, the conclusion is that I should not have the power retract memberships, only the power the delete the team.

Sure, nothing you've said is in conflict with what I'm proposing, is it?

-Rob

Revision history for this message
Curtis Hovey (sinzui) wrote :

Your proposal does conflict. I cannot have access to retractTeamMembership. Admins are not asking for script. In fact, they are asking why they are assigned tasks to involving user data--Lp should be run by users and expert teams

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Dec 13, 2010 at 9:36 AM, Curtis Hovey
<email address hidden> wrote:
> Your proposal does conflict. I cannot have access to retractTeamMembership. Admins are not asking for script. In fact, they are asking why they are assigned tasks to involving user data--Lp should be run by users and expert teams

I haven't proposed that you have access to retractTeamMembership.

-Rob

Revision history for this message
Curtis Hovey (sinzui) wrote :

What are you proposing then?

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Dec 13, 2010 at 10:07 AM, Curtis Hovey
<email address hidden> wrote:
> What are you proposing then?

That the request to delete the team be forwarded to model code, and
there the model code can call retractTeam in confidence, without
needing to remove the security proxy.

E.g. PersonOrTeam.deleteSelf()

There is existing code doing this, spelt slightly differently (it
might be nice to use the same method name, I just don't recall it
offhand).

-Rob

Revision history for this message
Curtis Hovey (sinzui) wrote :

Sorry for being obtuse. I now understand your suggestion. Yes we should move these rules into the model. Edwin is landing a more complicated rule at the moment. I will refactor my branch once his changes are in trunk.

The method will behave like other dangerous delete methods--raise an error if the team has mailing-lists or PPAs that the user needs to review and disable separately. The membership removals and auto purging of lists and emails will be managed by the method.

Revision history for this message
Henning Eggers (henninge) wrote :

Looks fine to me, except I don't see a reason why removeSecurityProxy should not be imported at the top of the file. Please see if you can move that.
Also, you mentioned on IRC that you wanted to add a comment to the test.

Thanks for the fix! ;)

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/registry/browser/peoplemerge.py'
2--- lib/lp/registry/browser/peoplemerge.py 2010-12-03 16:33:03 +0000
3+++ lib/lp/registry/browser/peoplemerge.py 2010-12-13 16:32:06 +0000
4@@ -16,6 +16,7 @@
5
6
7 from zope.component import getUtility
8+from zope.security.proxy import removeSecurityProxy
9
10 from canonical.database.sqlbase import flush_database_updates
11 from canonical.launchpad import _
12@@ -89,7 +90,6 @@
13 logintokenset = getUtility(ILoginTokenSet)
14 # Need to remove the security proxy because the dupe account may have
15 # hidden email addresses.
16- from zope.security.proxy import removeSecurityProxy
17 token = logintokenset.new(
18 self.user, login, removeSecurityProxy(email).email,
19 LoginTokenType.ACCOUNTMERGE)
20@@ -153,7 +153,6 @@
21
22 def doMerge(self, data):
23 """Merge the two person/team entries specified in the form."""
24- from zope.security.proxy import removeSecurityProxy
25 for email in self.dupe_person_emails:
26 email = IMasterObject(email)
27 # EmailAddress.person and EmailAddress.account are readonly
28@@ -241,10 +240,18 @@
29 # Team email addresses are not transferable.
30 self.dupe_person.setContactAddress(None)
31 # The registry experts does not want to acquire super teams from a
32- # merge.
33+ # merge. This operation requires unrestricted access to ensure
34+ # the user who has permission to delete a team can remove the
35+ # team from other teams.
36 if self.target_person == self.registry_experts:
37- for team in self.dupe_person.teams_participated_in:
38- self.dupe_person.retractTeamMembership(team, self.user)
39+ all_super_teams = set(self.dupe_person.teams_participated_in)
40+ indirect_super_teams = set(
41+ self.dupe_person.teams_indirectly_participated_in)
42+ super_teams = all_super_teams - indirect_super_teams
43+ naked_dupe_person = removeSecurityProxy(self.dupe_person)
44+ for team in super_teams:
45+ naked_dupe_person.retractTeamMembership(team, self.user)
46+ del naked_dupe_person
47 # We have sent another series of calls to the db, potentially a long
48 # sequence depending on the merge. We want everything synced up
49 # before proceeding.
50@@ -398,7 +405,6 @@
51 assert result_count == 1
52 # Need to remove the security proxy because the dupe account may have
53 # hidden email addresses.
54- from zope.security.proxy import removeSecurityProxy
55 self.dupe_email = removeSecurityProxy(results[0]).email
56
57 def render(self):
58@@ -446,7 +452,6 @@
59 # If the email addresses are hidden we must send a merge request
60 # to each of them. But first we've got to remove the security
61 # proxy so we can get to them.
62- from zope.security.proxy import removeSecurityProxy
63 email_addresses = [removeSecurityProxy(email).email
64 for email in self.dupeemails]
65 else:
66
67=== modified file 'lib/lp/registry/browser/tests/test_peoplemerge.py'
68--- lib/lp/registry/browser/tests/test_peoplemerge.py 2010-12-03 16:33:03 +0000
69+++ lib/lp/registry/browser/tests/test_peoplemerge.py 2010-12-13 16:32:06 +0000
70@@ -15,6 +15,7 @@
71 from lp.registry.interfaces.person import IPersonSet
72 from lp.registry.interfaces.mailinglist import MailingListStatus
73 from lp.testing import (
74+ celebrity_logged_in,
75 login_celebrity,
76 login_person,
77 person_logged_in,
78@@ -141,8 +142,7 @@
79 self.assertEqual([], view.errors)
80 self.assertEqual(self.target_team, self.dupe_team.merged)
81
82- def test_merge_team_as_delete_with_super_teams_into_registry_experts(
83- self):
84+ def test_owner_delete_team_with_super_teams(self):
85 # Super team memberships are removed.
86 self.target_team = getUtility(ILaunchpadCelebrities).registry_experts
87 super_team = self.factory.makeTeam()
88@@ -152,3 +152,15 @@
89 view = self.getView()
90 self.assertEqual([], view.errors)
91 self.assertEqual(self.target_team, self.dupe_team.merged)
92+
93+ def test_registry_delete_team_with_super_teams(self):
94+ # Registry admins can delete teams with super team memberships.
95+ self.target_team = getUtility(ILaunchpadCelebrities).registry_experts
96+ super_team = self.factory.makeTeam()
97+ # Use admin to avoid the team invitation dance. The Registry admin
98+ # is logged back in.
99+ with celebrity_logged_in('admin'):
100+ self.dupe_team.join(super_team, super_team.teamowner)
101+ view = self.getView()
102+ self.assertEqual([], view.errors)
103+ self.assertEqual(self.target_team, self.dupe_team.merged)