Merge lp:~edwin-grubbs/launchpad/bug-506965-merge-team-oops into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Approved by: Curtis Hovey
Approved revision: not available
Merged at revision: not available
Proposed branch: lp:~edwin-grubbs/launchpad/bug-506965-merge-team-oops
Merge into: lp:launchpad
Diff against target: 246 lines (+82/-43)
4 files modified
lib/lp/registry/browser/menu.py (+1/-1)
lib/lp/registry/browser/peoplemerge.py (+4/-4)
lib/lp/registry/browser/tests/peoplemerge-views.txt (+34/-4)
lib/lp/registry/stories/team/xx-adminteammerge.txt (+43/-34)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-506965-merge-team-oops
Reviewer Review Type Date Requested Status
Curtis Hovey (community) Approve
Review via email: mp+17445@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Summary
-------

Fixed oops caused when a registry expert (non-admin) merges a team with an email address into another team.

Implementation details
----------------------

Set email status after removing security adapter to give the registry expert access in this special case.

Tests
-----

./bin/test -vv -t xx-adminteammerge.txt

Demo and Q/A
------------

* Add ~no-priv to the ~registry team.
* Login as ~no-priv.
* Open http://launchpad.dev/people/+newteam
  * Add a team with a contact email address.
  * You will need to verify the email address with the token it emails you. (On launchpad.dev, you can just look in the logintoken table and paste the token into https://launchpad.dev/token/$token)
* Open http://launchpad.dev/people/
  * Click on "Merge teams"
  * Enter you new team for the first team.
  * Enter "ubuntu-team" for the second team.
  * Click "Merge"
  * When you get a warning message, click "Deactivate members and merge".

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

Hi Edwin.

There are some dangerous changes in the code and the test does is not what is happening in production.

We need to revert the permissions on the view. It must be admin because we do not want every team owner/admin permission to merge their team with *any* other team. Delete restricts the operation to the context team and ~registry.

> Demo and Q/A
> ------------
>
> * Add ~no-priv to the ~registry team.
> * Login as ~no-priv.
> * Open http://launchpad.dev/people/+newteam
> * Add a team with a contact email address.
> * You will need to verify the email address with the token it emails you.
> (On launchpad.dev, you can just look in the logintoken table and paste the
> token into https://launchpad.dev/token/$token)
> * Open http://launchpad.dev/people/
> * Click on "Merge teams"
> * Enter you new team for the first team.
> * Enter "ubuntu-team" for the second team.
> * Click "Merge"
> * When you get a warning message, click "Deactivate members and merge".

This story is wrong. The delete team scenario is a very restricted merge operation that registry administators may perform. Admins can do unrestricted merges.
    * As registry expert.
    * Visit https://edge.launchpad.net/~consulvision
      * This team has an email address in NEW status
      * team email addresses are not shown in the UI
      * only the team owner can access the address.
    * Choose Delete team.
    * Verify the team is gone.

The code change is the the view, so we want a test that verifies the view handles the real conditions. The test should be in lp/registry/tests/peoplemerge-views.txt. You can update the the Delete team section to cover this story
    * Create a team with an email address.
    * deletable_.setContactAddress(None)
    * login_person(registry_experts.teamowner)
    * form = {'field.actions.delete': 'Delete'}
    * view = create_initialized_view(deletable_team, '+delete', form=form)
    * view.errors
      []
    * for notification in view.request.response.notifications:
         print notification.message
      Team deleted.

review: Needs Fixing (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :

> * Create a team with an email address.
> * deletable_.setContactAddress(None)

I think I meant deletable_team.

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :
Download full text (5.8 KiB)

> Hi Edwin.
>
> There are some dangerous changes in the code and the test does is not what is
> happening in production.
>
> We need to revert the permissions on the view. It must be admin because we do
> not want every team owner/admin permission to merge their team with *any*
> other team. Delete restricts the operation to the context team and ~registry.

Hi Sinzui,

Thanks for the review. As discussed on IRC, the permissions for the view and for the +adminmergeteam link are fine since it uses launchpad.Moderate on IPersonSet, which does not include admins of any team.

> > Demo and Q/A
> > ------------
> >
> > * Add ~no-priv to the ~registry team.
> > * Login as ~no-priv.
> > * Open http://launchpad.dev/people/+newteam
> > * Add a team with a contact email address.
> > * You will need to verify the email address with the token it emails you.
> > (On launchpad.dev, you can just look in the logintoken table and paste the
> > token into https://launchpad.dev/token/$token)
> > * Open http://launchpad.dev/people/
> > * Click on "Merge teams"
> > * Enter you new team for the first team.
> > * Enter "ubuntu-team" for the second team.
> > * Click "Merge"
> > * When you get a warning message, click "Deactivate members and merge".
>
> This story is wrong. The delete team scenario is a very restricted merge
> operation that registry administators may perform. Admins can do unrestricted
> merges.
> * As registry expert.
> * Visit https://edge.launchpad.net/~consulvision
> * This team has an email address in NEW status
> * team email addresses are not shown in the UI
> * only the team owner can access the address.
> * Choose Delete team.
> * Verify the team is gone.
>
> The code change is the the view, so we want a test that verifies the view
> handles the real conditions. The test should be in lp/registry/tests
> /peoplemerge-views.txt. You can update the the Delete team section to cover
> this story
> * Create a team with an email address.
> * deletable_.setContactAddress(None)
> * login_person(registry_experts.teamowner)
> * form = {'field.actions.delete': 'Delete'}
> * view = create_initialized_view(deletable_team, '+delete', form=form)
> * view.errors
> []
> * for notification in view.request.response.notifications:
> print notification.message
> Team deleted.

I have added this test. I also changed other parts of the test that was using the owner of the registry team, which is mark. Since mark is also in the admin team, I don't think it is a very effective test of the registry experts abilities to use him.

-Edwin

Incremental diff:
{{{
=== modified file 'lib/lp/registry/browser/tests/peoplemerge-views.txt'
--- lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-01-06 17:52:45 +0000
+++ lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-01-19 23:53:42 +0000
@@ -6,9 +6,17 @@
 Team Merges
 -----------

+Create a member of the registry team that is not a member of the admins
+team.
+
     >>> from lp.registry.interfaces.person import IPersonSet
+ >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
+ >>> registry_e...

Read more...

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

Hi Edwin.

Thank you for correcting the registry-experts test. I am sure something
bad would have happened using a super-powerful moderator. This did
happen to me in a test for mirror admins, which user Mark owns.

I have one small suggestion for the test.

  review approve

On Wed, 2010-01-20 at 00:02 +0000, Edwin Grubbs wrote:
> Incremental diff:
> {{{
> === modified file 'lib/lp/registry/browser/tests/peoplemerge-views.txt'
> --- lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-01-06 17:52:45 +0000
> +++ lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-01-19 23:53:42 +0000
> @@ -6,9 +6,17 @@
> Team Merges
> -----------
...

> @@ -39,6 +47,7 @@
> >>> parent_team = factory.makeTeam()
> >>> child_team = factory.makeTeam(name='child-team')
> >>> random_team = factory.makeTeam()
> + >>> login('<email address hidden>')

Use login('<email address hidden>') so that the role is clear.

...

--
__Curtis C. Hovey_________
http://launchpad.net/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/registry/browser/menu.py'
2--- lib/lp/registry/browser/menu.py 2009-08-21 20:04:25 +0000
3+++ lib/lp/registry/browser/menu.py 2010-01-20 00:01:15 +0000
4@@ -60,7 +60,7 @@
5 text = 'Merge people'
6 return Link('/people/+adminpeoplemerge', text, icon='edit')
7
8- @enabled_with_permission('launchpad.Admin')
9+ @enabled_with_permission('launchpad.Moderate')
10 def admin_merge_teams(self):
11 text = 'Merge teams'
12 return Link('/people/+adminteammerge', text, icon='edit')
13
14=== modified file 'lib/lp/registry/browser/peoplemerge.py'
15--- lib/lp/registry/browser/peoplemerge.py 2010-01-12 00:19:20 +0000
16+++ lib/lp/registry/browser/peoplemerge.py 2010-01-20 00:01:15 +0000
17@@ -140,15 +140,15 @@
18 from zope.security.proxy import removeSecurityProxy
19 for email in self.dupe_person_emails:
20 email = IMasterObject(email)
21- # XXX: Guilherme Salgado 2007-10-15: Maybe this status change
22- # should be done only when merging people but not when merging
23- # teams.
24- email.status = EmailAddressStatus.NEW
25 # EmailAddress.person and EmailAddress.account are readonly
26 # fields, so we need to remove the security proxy here.
27 naked_email = removeSecurityProxy(email)
28 naked_email.personID = self.target_person.id
29 naked_email.accountID = self.target_person.accountID
30+ # XXX: Guilherme Salgado 2007-10-15: Maybe this status change
31+ # should be done only when merging people but not when merging
32+ # teams.
33+ naked_email.status = EmailAddressStatus.NEW
34 flush_database_updates()
35 getUtility(IPersonSet).merge(self.dupe_person, self.target_person)
36 self.request.response.addInfoNotification(self.merge_message)
37
38=== modified file 'lib/lp/registry/browser/tests/peoplemerge-views.txt'
39--- lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-01-06 17:52:45 +0000
40+++ lib/lp/registry/browser/tests/peoplemerge-views.txt 2010-01-20 00:01:15 +0000
41@@ -6,9 +6,17 @@
42 Team Merges
43 -----------
44
45+Create a member of the registry team that is not a member of the admins
46+team.
47+
48 >>> from lp.registry.interfaces.person import IPersonSet
49+ >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
50+ >>> registry_experts = getUtility(ILaunchpadCelebrities).registry_experts
51 >>> person_set = getUtility(IPersonSet)
52- >>> registry_expert = person_set.getByName('mark')
53+ >>> registry_expert = factory.makePerson()
54+ >>> login_person(registry_experts.teamowner)
55+ >>> ignored = registry_experts.addMember(
56+ ... registry_expert, registry_experts.teamowner)
57 >>> login_person(registry_expert)
58
59 A team (name21) can be merged into another (ubuntu-team).
60@@ -39,6 +47,7 @@
61 >>> parent_team = factory.makeTeam()
62 >>> child_team = factory.makeTeam(name='child-team')
63 >>> random_team = factory.makeTeam()
64+ >>> login('foo.bar@canonical.com')
65 >>> ignored = parent_team.addMember(
66 ... child_team, reviewer=parent_team.teamowner, force_team_add=True)
67 >>> form = {'field.dupe_person': child_team.name,
68@@ -112,10 +121,8 @@
69 Users with launchpad.Moderate such as team admins and registry experts
70 can delete teams.
71
72- >>> from canonical.launchpad.interfaces import ILaunchpadCelebrities
73 >>> from canonical.launchpad.webapp.authorization import check_permission
74
75- >>> registry_experts = getUtility(ILaunchpadCelebrities).registry_experts
76 >>> team_owner = factory.makePerson()
77 >>> team_member = factory.makePerson()
78 >>> deletable_team = factory.makeTeam(owner=team_owner, name='deletable')
79@@ -127,7 +134,7 @@
80 >>> check_permission('launchpad.Moderate', view)
81 False
82
83- >>> login_person(registry_experts.teamowner)
84+ >>> login_person(registry_expert)
85 >>> check_permission('launchpad.Moderate', view)
86 True
87
88@@ -241,3 +248,26 @@
89
90 >>> print find_tag_by_id(content, 'field.actions.delete')
91 None
92+
93+The registry experts should be able to delete a team with an
94+validated email address, which will be invisible, since only
95+preferred email addresses are shown for teams.
96+
97+ >>> from canonical.launchpad.interfaces.emailaddress import (
98+ ... IEmailAddressSet)
99+ >>> login_person(registry_expert)
100+ >>> admin = getUtility(ILaunchpadCelebrities).admin
101+ >>> registry_expert.inTeam(admin)
102+ False
103+ >>> deletable_team = factory.makeTeam(email="del@example.com")
104+ >>> deletable_team.setContactAddress(None)
105+ >>> for email in getUtility(IEmailAddressSet).getByPerson(deletable_team):
106+ ... print email.email, email.status.title
107+ del@example.com Validated Email Address
108+ >>> form = {'field.actions.delete': 'Delete'}
109+ >>> view = create_initialized_view(deletable_team, '+delete', form=form)
110+ >>> view.errors
111+ []
112+ >>> for notification in view.request.response.notifications:
113+ ... print notification.message
114+ Team deleted.
115
116=== modified file 'lib/lp/registry/stories/team/xx-adminteammerge.txt'
117--- lib/lp/registry/stories/team/xx-adminteammerge.txt 2009-11-22 15:43:16 +0000
118+++ lib/lp/registry/stories/team/xx-adminteammerge.txt 2010-01-20 00:01:15 +0000
119@@ -4,30 +4,39 @@
120 active members, so the user will first have to confirm that the
121 members should be deactivated before the teams are merged.
122
123- >>> admin_browser.open('http://launchpad.dev/people/+adminteammerge')
124- >>> admin_browser.getControl('Duplicated Team').value = (
125- ... 'landscape-developers')
126- >>> admin_browser.getControl('Target Team').value = 'guadamen'
127- >>> admin_browser.getControl('Merge').click()
128+ >>> from zope.component import getUtility
129+ >>> from lp.registry.interfaces.person import IPersonSet
130+ >>> login('foo.bar@canonical.com')
131+ >>> registry_expert = factory.makePerson(
132+ ... email='reg@example.com', password='test')
133+ >>> new_team = factory.makeTeam(
134+ ... name="new-team", email="new_team@example.com")
135+ >>> registry_experts = getUtility(IPersonSet).getByName('registry')
136+ >>> ignored = registry_experts.addMember(
137+ ... registry_expert, registry_experts.teamowner)
138+ >>> logout()
139+ >>> registry_browser = setupBrowser(auth='Basic reg@example.com:test')
140+ >>> registry_browser.open('http://launchpad.dev/people/+adminteammerge')
141+ >>> registry_browser.getControl('Duplicated Team').value = (
142+ ... 'new-team')
143+ >>> registry_browser.getControl('Target Team').value = 'guadamen'
144+ >>> registry_browser.getControl('Merge').click()
145
146- >>> admin_browser.url
147+ >>> registry_browser.url
148 'http://launchpad.dev/people/+adminteammerge'
149- >>> print get_feedback_messages(admin_browser.contents)[0]
150- Landscape Developers has 2 active members which will have to be
151- deactivated before the teams can be merged.
152+ >>> print get_feedback_messages(registry_browser.contents)[0]
153+ New Team has 1 active members which will have to be deactivated
154+ before the teams can be merged.
155
156- >>> admin_browser.getControl('Deactivate Members and Merge').click()
157- >>> admin_browser.url
158+ >>> registry_browser.getControl('Deactivate Members and Merge').click()
159+ >>> registry_browser.url
160 'http://launchpad.dev/~guadamen'
161
162 >>> from canonical.launchpad.ftests import ANONYMOUS, login, logout
163- >>> from canonical.launchpad.interfaces import IMailingListSet, IPersonSet
164+ >>> from canonical.launchpad.interfaces import IMailingListSet
165 >>> from zope.component import getUtility
166 >>> login(ANONYMOUS)
167- >>> person_set = getUtility(IPersonSet)
168- >>> landscape = person_set.getByName(
169- ... 'landscape-developers-merged', ignore_merged=False)
170- >>> landscape.merged.name
171+ >>> new_team.merged.name
172 u'guadamen'
173 >>> logout()
174
175@@ -36,7 +45,7 @@
176
177 Merged teams are not visible anymore.
178
179- >>> browser.open("http://launchpad.dev/~landscape-developers-merged")
180+ >>> browser.open("http://launchpad.dev/~new-team-merged")
181 Traceback (most recent call last):
182 ...
183 NotFound:...
184@@ -48,45 +57,45 @@
185 merged, though.
186
187 >>> login(ANONYMOUS)
188- >>> guadamen = person_set.getByName('guadamen')
189+ >>> guadamen = getUtility(IPersonSet).getByName('guadamen')
190 >>> mailing_list = getUtility(IMailingListSet).new(guadamen)
191 >>> logout()
192
193- >>> admin_browser.open('http://launchpad.dev/people/+adminteammerge')
194- >>> admin_browser.getControl('Duplicated Team').value = 'guadamen'
195- >>> admin_browser.getControl('Target Team').value = 'ubuntu-team'
196- >>> admin_browser.getControl('Merge').click()
197+ >>> registry_browser.open('http://launchpad.dev/people/+adminteammerge')
198+ >>> registry_browser.getControl('Duplicated Team').value = 'guadamen'
199+ >>> registry_browser.getControl('Target Team').value = 'ubuntu-team'
200+ >>> registry_browser.getControl('Merge').click()
201
202- >>> admin_browser.url
203+ >>> registry_browser.url
204 'http://launchpad.dev/people/+adminteammerge'
205
206- >>> print get_feedback_messages(admin_browser.contents)
207+ >>> print get_feedback_messages(registry_browser.contents)
208 [u'There is 1 error.',
209 u"guadamen is associated with a Launchpad mailing list;
210 we can't merge it."]
211
212 We also can't (for obvious reasons) merge any person/team into itself.
213
214- >>> admin_browser.getControl('Duplicated Team').value = 'shipit-admins'
215- >>> admin_browser.getControl('Target Team').value = 'shipit-admins'
216- >>> admin_browser.getControl('Merge').click()
217+ >>> registry_browser.getControl('Duplicated Team').value = 'shipit-admins'
218+ >>> registry_browser.getControl('Target Team').value = 'shipit-admins'
219+ >>> registry_browser.getControl('Merge').click()
220
221- >>> admin_browser.url
222+ >>> registry_browser.url
223 'http://launchpad.dev/people/+adminteammerge'
224
225- >>> print get_feedback_messages(admin_browser.contents)
226+ >>> print get_feedback_messages(registry_browser.contents)
227 [u'There is 1 error.', u"You can't merge shipit-admins into itself."]
228
229 Nor can we merge a person into a team or a team into a person.
230
231- >>> admin_browser.getControl('Duplicated Team').value = 'ubuntu-team'
232- >>> admin_browser.getControl('Target Team').value = 'salgado'
233- >>> admin_browser.getControl('Merge').click()
234+ >>> registry_browser.getControl('Duplicated Team').value = 'ubuntu-team'
235+ >>> registry_browser.getControl('Target Team').value = 'salgado'
236+ >>> registry_browser.getControl('Merge').click()
237
238- >>> admin_browser.url
239+ >>> registry_browser.url
240 'http://launchpad.dev/people/+adminteammerge'
241
242 # Yes, the error message is not of much help, but this UI is only for
243 # admins and they're supposed to know what they're doing.
244- >>> print get_feedback_messages(admin_browser.contents)
245+ >>> print get_feedback_messages(registry_browser.contents)
246 [u'There is 1 error.', u'Constraint not satisfied']