Merge lp:~jcsackett/launchpad/anonymous-api-access-emails-681815 into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 12048
Proposed branch: lp:~jcsackett/launchpad/anonymous-api-access-emails-681815
Merge into: lp:launchpad
Diff against target: 1048 lines (+205/-101)
32 files modified
lib/canonical/launchpad/browser/tests/test_logintoken.py (+2/-1)
lib/canonical/launchpad/doc/emailaddress.txt (+1/-0)
lib/canonical/launchpad/doc/location-widget.txt (+3/-3)
lib/canonical/launchpad/doc/notification-recipient-set.txt (+1/-0)
lib/canonical/launchpad/doc/vocabulary-json.txt (+1/-1)
lib/canonical/launchpad/security.py (+2/-5)
lib/canonical/launchpad/webapp/tests/test_login.py (+3/-2)
lib/canonical/launchpad/webapp/tests/test_loginsource.py (+4/-3)
lib/lp/blueprints/stories/standalone/subscribing.txt (+2/-2)
lib/lp/code/browser/tests/test_branch.py (+1/-1)
lib/lp/code/mail/branch.py (+4/-2)
lib/lp/code/mail/tests/test_branch.py (+4/-1)
lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py (+1/-1)
lib/lp/code/model/recipebuilder.py (+6/-1)
lib/lp/code/model/tests/test_recipebuilder.py (+1/-0)
lib/lp/code/stories/feeds/xx-revision-atom.txt (+2/-1)
lib/lp/code/tests/test_directbranchcommit.py (+4/-1)
lib/lp/registry/browser/tests/person-views.txt (+4/-2)
lib/lp/registry/browser/tests/test_person_webservice.py (+41/-1)
lib/lp/registry/browser/tests/user-to-user-views.txt (+1/-1)
lib/lp/registry/doc/message-holds.txt (+1/-1)
lib/lp/registry/doc/person.txt (+3/-2)
lib/lp/registry/scripts/personnotification.py (+2/-1)
lib/lp/registry/stories/webservice/xx-person.txt (+81/-46)
lib/lp/registry/tests/test_personset.py (+3/-3)
lib/lp/registry/tests/test_product.py (+7/-6)
lib/lp/registry/tests/test_project.py (+2/-1)
lib/lp/services/mail/sendmail.py (+3/-1)
lib/lp/services/mailman/testing/__init__.py (+2/-1)
lib/lp/services/mailman/tests/test_lpmoderate.py (+4/-2)
lib/lp/testing/factory.py (+7/-6)
lib/lp/testing/tests/test_login.py (+2/-2)
To merge this branch: bzr merge lp:~jcsackett/launchpad/anonymous-api-access-emails-681815
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Benji York (community) code* Approve
Review via email: mp+42309@code.launchpad.net

Commit message

[r=benji,edwin-grubbs][ui=none][bug=681815] Patches a hole in the API allowing anonymous gathering of emails.

Description of the change

Summary
=======
The webservice currently has a hole through which a person can anonymously collect email addresses. This is because the security check employed for it allows email addresses to be returned if they're attached to a user. To be consistent with the web interface, you shouldn't get email addresses unless you're logged in.

This branch changes the unauthenticated check so that it never allows emails to be returned.

Proposed fix
============
Change the ViewEmailAddress checkUnauthenticated to always return false; there is never a circumstance in which the API should anonymously return emails.

Preimplementation talk
======================
Spoke with Curtis Hovey.

Implementation details
======================
As in Proposed.

Tests
=====
bin/test -t registry.*webservice.*xx-person\.txt

Demo & QA
=========
There are several scripts attached to the bug that should pass if this whole is patched.

Lint
====
make lint output:

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/canonical/launchpad/security.py
  lib/lp/registry/stories/webservice/xx-person.txt

./lib/canonical/launchpad/security.py
     733: E302 expected 2 blank lines, found 1
    1266: E302 expected 2 blank lines, found 1
    1476: E302 expected 2 blank lines, found 1
./lib/lp/registry/stories/webservice/xx-person.txt
      14: want exceeds 78 characters.
      16: want exceeds 78 characters.
      20: want exceeds 78 characters.
      38: want exceeds 78 characters.
      41: want exceeds 78 characters.
      44: want exceeds 78 characters.
      61: want exceeds 78 characters.
      63: want exceeds 78 characters.
      67: want exceeds 78 characters.
      69: want exceeds 78 characters.
      72: want exceeds 78 characters.
      86: want exceeds 78 characters.
      87: want exceeds 78 characters.
      90: want exceeds 78 characters.
      93: want exceeds 78 characters.
      95: want exceeds 78 characters.
     161: source exceeds 78 characters.
     162: source exceeds 78 characters.
     163: want has trailing whitespace.
     191: narrative exceeds 78 characters.
     266: source exceeds 78 characters.
     349: narrative has trailing whitespace.
     359: narrative has trailing whitespace.
     482: source exceeds 78 characters.
     506: source exceeds 78 characters.
     569: source exceeds 78 characters.
     822: source has bad indentation.

The blank line error in security.py is because of lint's issues with comments and blanklines.

The wants over 78 lines don't appear to be something that can easily be moved about; they are arguably more readable in their current form.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This branch looks good. The reST-ification of the section headings is a nice touch.

As for the long "want" lines, the ones I looked at where of the form "foo: bar\n", so could be wrapped to "foo:\n bar\n" (i.e., line break at the colon and indent the next line) and still retain their readability. However, I agree that their current arrangement isn't too bad either.

review: Approve (code*)
Revision history for this message
j.c.sackett (jcsackett) wrote :

I've pushed up a change which makes this a better test, but it's still going to pass in either event; oddly, while in test mode, this security hole doesn't seem to exist.

I encourage any thoughts as to how to deal with this; otherwise, I think the tests show this patch hasn't broken anything, and the scripts attached to the bug can be used to QA it once it's live on qastaging.

Revision history for this message
j.c.sackett (jcsackett) wrote :

The big change is the new tests. I've attached a diff of just those.

1=== modified file 'lib/lp/registry/browser/tests/test_person_webservice.py'
2--- lib/lp/registry/browser/tests/test_person_webservice.py 2010-10-04 19:50:45 +0000
3+++ lib/lp/registry/browser/tests/test_person_webservice.py 2010-12-02 17:53:46 +0000
4@@ -5,12 +5,52 @@
5
6 import unittest
7
8+from zope.security.management import endInteraction
9 from zope.security.proxy import removeSecurityProxy
10
11 from canonical.launchpad.ftests import login
12 from canonical.launchpad.testing.pages import LaunchpadWebServiceCaller
13 from canonical.testing.layers import DatabaseFunctionalLayer
14-from lp.testing import TestCaseWithFactory
15+from lp.testing import (
16+ launchpadlib_for,
17+ launchpadlib_for_anonymous,
18+ TestCaseWithFactory,
19+ )
20+
21+
22+class TestPersonEmailSecurity(TestCaseWithFactory):
23+
24+ layer = DatabaseFunctionalLayer
25+
26+ def setUp(self):
27+ super(TestPersonEmailSecurity, self).setUp()
28+ self.target = self.factory.makePerson(name='target')
29+ self.email_one = self.factory.makeEmail(
30+ 'test1@example.com', self.target)
31+ self.email_two = self.factory.makeEmail(
32+ 'test2@example.com', self.target)
33+
34+ def test_logged_in_can_access(self):
35+ # A logged in launchpadlib connection can see confirmed email
36+ # addresses.
37+ accessor = self.factory.makePerson()
38+ lp = launchpadlib_for("test", accessor.name)
39+ person = lp.people['target']
40+ emails = sorted(list(person.confirmed_email_addresses))
41+ self.assertNotEqual(
42+ sorted([self.email_one, self.email_two]),
43+ len(emails))
44+
45+ def test_anonymous_cannot_access(self):
46+ # An anonymous launchpadlib connection cannot see email addresses.
47+
48+ # Need to endInteraction() because launchpadlib_for_anonymous() will
49+ # setup a new one.
50+ endInteraction()
51+ lp = launchpadlib_for_anonymous('test', version='devel')
52+ person = lp.people['target']
53+ emails = list(person.confirmed_email_addresses)
54+ self.assertEqual([], emails)
55
56
57 class TestPersonRepresentation(TestCaseWithFactory):
Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Hi JC,

Thanks for adding tests that actually fail without your changes to checkUnauthenticated(). I like benji's suggestion to wrap lines in the doctests to make lint complain less.

-Edwin

review: Approve (code)
Revision history for this message
j.c.sackett (jcsackett) wrote :

Edwin, Benji--

I've made the changes to make lint happy.

Thanks for the review, guys!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/tests/test_logintoken.py'
2--- lib/canonical/launchpad/browser/tests/test_logintoken.py 2010-10-04 19:50:45 +0000
3+++ lib/canonical/launchpad/browser/tests/test_logintoken.py 2010-12-13 13:34:01 +0000
4@@ -4,6 +4,7 @@
5 import unittest
6
7 from zope.component import getUtility
8+from zope.security.proxy import removeSecurityProxy
9
10 from canonical.launchpad.browser.logintoken import (
11 ClaimTeamView,
12@@ -29,7 +30,7 @@
13 def setUp(self):
14 TestCaseWithFactory.setUp(self)
15 self.person = self.factory.makePerson(name='test-user')
16- self.email = self.person.preferredemail.email
17+ self.email = removeSecurityProxy(self.person).preferredemail.email
18 self.expected_next_url = 'http://127.0.0.1/~test-user'
19
20 def test_ClaimTeamView(self):
21
22=== modified file 'lib/canonical/launchpad/doc/emailaddress.txt'
23--- lib/canonical/launchpad/doc/emailaddress.txt 2010-11-15 21:19:45 +0000
24+++ lib/canonical/launchpad/doc/emailaddress.txt 2010-12-13 13:34:01 +0000
25@@ -12,6 +12,7 @@
26
27 We can get an email address through IEmailAddressSet.getByEmail().
28
29+ >>> login_person(person)
30 >>> email = emailset.getByEmail(person.preferredemail.email)
31 >>> email == person.preferredemail
32 True
33
34=== modified file 'lib/canonical/launchpad/doc/location-widget.txt'
35--- lib/canonical/launchpad/doc/location-widget.txt 2010-11-16 18:55:26 +0000
36+++ lib/canonical/launchpad/doc/location-widget.txt 2010-12-13 13:34:01 +0000
37@@ -28,7 +28,7 @@
38 map around the location where he seems to be, based on the IP address of
39 the request.
40
41- >>> login(salgado.preferredemail.email)
42+ >>> login_person(salgado)
43 >>> widget = LocationWidget(bound_field, request)
44 >>> widget.zoom
45 7
46@@ -65,7 +65,7 @@
47
48 >>> kamion = getUtility(IPersonSet).getByName('kamion')
49 >>> bound_field = field.bind(kamion)
50- >>> login(kamion.preferredemail.email)
51+ >>> login_person(kamion)
52 >>> widget = LocationWidget(bound_field, request)
53 >>> widget.zoom
54 9
55@@ -148,7 +148,7 @@
56 >>> bound_field = field.bind(kamion)
57 >>> request = LaunchpadTestRequest(
58 ... environ={'REMOTE_ADDR': '201.13.165.145'})
59- >>> login(kamion.preferredemail.email)
60+ >>> login_person(kamion)
61 >>> widget = LocationWidget(bound_field, request)
62 >>> print widget()
63 <input...name="field.location.latitude"...type="hidden"...
64
65=== modified file 'lib/canonical/launchpad/doc/notification-recipient-set.txt'
66--- lib/canonical/launchpad/doc/notification-recipient-set.txt 2010-10-18 22:24:59 +0000
67+++ lib/canonical/launchpad/doc/notification-recipient-set.txt 2010-12-13 13:34:01 +0000
68@@ -123,6 +123,7 @@
69 other person:
70
71 >>> ubuntu_team = person_set.getByName('ubuntu-team')
72+ >>> login_person(ubuntu_team.teamowner)
73 >>> print ubuntu_team.preferredemail.email
74 support@ubuntu.com
75 >>> recipients.add(ubuntu_team, 'You are notified for fun.', 'Fun')
76
77=== modified file 'lib/canonical/launchpad/doc/vocabulary-json.txt'
78--- lib/canonical/launchpad/doc/vocabulary-json.txt 2010-10-03 15:30:06 +0000
79+++ lib/canonical/launchpad/doc/vocabulary-json.txt 2010-12-13 13:34:01 +0000
80@@ -96,7 +96,7 @@
81 {
82 "api_uri": "/~name16",
83 "css": "sprite person",
84- "description": "foo.bar@canonical.com",
85+ "description": "<email address hidden>",
86 "title": "Foo Bar",
87 "value": "name16"
88 }
89
90=== modified file 'lib/canonical/launchpad/security.py'
91--- lib/canonical/launchpad/security.py 2010-12-07 14:15:37 +0000
92+++ lib/canonical/launchpad/security.py 2010-12-13 13:34:01 +0000
93@@ -2422,11 +2422,8 @@
94
95 def checkUnauthenticated(self):
96 """See `AuthorizationBase`."""
97- # Email addresses without an associated Person cannot be seen by
98- # anonymous users.
99- if self.obj.person is None:
100- return False
101- return not self.obj.person.hide_email_addresses
102+ # Anonymous users can never see email addresses.
103+ return False
104
105 def checkAccountAuthenticated(self, account):
106 """Can the user see the details of this email address?
107
108=== modified file 'lib/canonical/launchpad/webapp/tests/test_login.py'
109--- lib/canonical/launchpad/webapp/tests/test_login.py 2010-10-27 14:20:21 +0000
110+++ lib/canonical/launchpad/webapp/tests/test_login.py 2010-12-13 13:34:01 +0000
111@@ -188,10 +188,11 @@
112 def test_full_fledged_account(self):
113 # In the common case we just login and redirect to the URL specified
114 # in the 'starting_url' query arg.
115- person = self.factory.makePerson()
116+ test_email = 'test-example@example.com'
117+ person = self.factory.makePerson(email=test_email)
118 with SRegResponse_fromSuccessResponse_stubbed():
119 view, html = self._createViewWithResponse(
120- person.account, email=person.preferredemail.email)
121+ person.account, email=test_email)
122 self.assertTrue(view.login_called)
123 response = view.request.response
124 self.assertEquals(httplib.TEMPORARY_REDIRECT, response.getStatus())
125
126=== modified file 'lib/canonical/launchpad/webapp/tests/test_loginsource.py'
127--- lib/canonical/launchpad/webapp/tests/test_loginsource.py 2010-10-20 20:51:26 +0000
128+++ lib/canonical/launchpad/webapp/tests/test_loginsource.py 2010-12-13 13:34:01 +0000
129@@ -4,6 +4,7 @@
130 import unittest
131
132 from zope.component import getUtility
133+from zope.security.proxy import removeSecurityProxy
134
135 from canonical.launchpad.ftests import (
136 ANONYMOUS,
137@@ -29,8 +30,8 @@
138 """
139 principal = self.login_source.getPrincipal(self.mark.account.id)
140 self.assertEqual(principal.access_level, AccessLevel.WRITE_PRIVATE)
141- principal = self.login_source.getPrincipalByLogin(
142- self.mark.preferredemail.email)
143+ marks_email = removeSecurityProxy(self.mark).preferredemail.email
144+ principal = self.login_source.getPrincipalByLogin(marks_email)
145 self.assertEqual(principal.access_level, AccessLevel.WRITE_PRIVATE)
146
147 def test_given_access_level_is_used(self):
148@@ -41,7 +42,7 @@
149 self.mark.account.id, access_level=AccessLevel.WRITE_PUBLIC)
150 self.assertEqual(principal.access_level, AccessLevel.WRITE_PUBLIC)
151 principal = self.login_source.getPrincipalByLogin(
152- self.mark.preferredemail.email, AccessLevel.READ_PUBLIC)
153+ removeSecurityProxy(self.mark).preferredemail.email, AccessLevel.READ_PUBLIC)
154 self.assertEqual(principal.access_level, AccessLevel.READ_PUBLIC)
155
156
157
158=== modified file 'lib/lp/blueprints/stories/standalone/subscribing.txt'
159--- lib/lp/blueprints/stories/standalone/subscribing.txt 2010-07-30 12:56:27 +0000
160+++ lib/lp/blueprints/stories/standalone/subscribing.txt 2010-12-13 13:34:01 +0000
161@@ -235,7 +235,7 @@
162 We subscribe the Ubuntu Team and an email is sent to the team's
163 preferred email address.
164
165- >>> login(ANONYMOUS)
166+ >>> login('no-priv@canonical.com')
167 >>> ([message['To'] for message in pop_notifications()] ==
168 ... [str(ubuntu_team.preferredemail.email)])
169 True
170@@ -250,7 +250,7 @@
171 We modified the Ubuntu Team's subscription and again, an email is sent
172 to the team's preferred email address.
173
174- >>> login(ANONYMOUS)
175+ >>> login('no-priv@canonical.com')
176 >>> ([message['To'] for message in pop_notifications()] ==
177 ... [str(ubuntu_team.preferredemail.email)])
178 True
179
180=== modified file 'lib/lp/code/browser/tests/test_branch.py'
181--- lib/lp/code/browser/tests/test_branch.py 2010-11-09 18:42:38 +0000
182+++ lib/lp/code/browser/tests/test_branch.py 2010-12-13 13:34:01 +0000
183@@ -172,7 +172,7 @@
184 """Registering a mirrored branch requests a mirror."""
185 arbitrary_person = self.factory.makePerson()
186 arbitrary_product = self.factory.makeProduct()
187- login(arbitrary_person.preferredemail.email)
188+ login_person(arbitrary_person)
189 try:
190 add_view = BranchAddView(arbitrary_person, self.request)
191 add_view.initialize()
192
193=== modified file 'lib/lp/code/mail/branch.py'
194--- lib/lp/code/mail/branch.py 2010-08-20 20:31:18 +0000
195+++ lib/lp/code/mail/branch.py 2010-12-13 13:34:01 +0000
196@@ -5,6 +5,7 @@
197
198 __metaclass__ = type
199
200+from zope.security.proxy import removeSecurityProxy
201
202 from canonical.launchpad.mail import format_address
203 from canonical.launchpad.webapp import canonical_url
204@@ -192,7 +193,7 @@
205 RecipientReason.forBranchSubscriber(
206 subscription, recipient, rationale)
207 from_address = format_address(
208- user.displayname, user.preferredemail.email)
209+ user.displayname, removeSecurityProxy(user).preferredemail.email)
210 return cls(
211 '[Branch %(unique_name)s]', 'branch-modified.txt',
212 actual_recipients, from_address, delta=delta,
213@@ -304,4 +305,5 @@
214
215 @staticmethod
216 def _format_user_address(user):
217- return format_address(user.displayname, user.preferredemail.email)
218+ naked_email = removeSecurityProxy(user).preferredemail.email
219+ return format_address(user.displayname, naked_email)
220
221=== modified file 'lib/lp/code/mail/tests/test_branch.py'
222--- lib/lp/code/mail/tests/test_branch.py 2010-10-04 19:50:45 +0000
223+++ lib/lp/code/mail/tests/test_branch.py 2010-12-13 13:34:01 +0000
224@@ -5,6 +5,8 @@
225
226 from unittest import TestLoader
227
228+from zope.security.proxy import removeSecurityProxy
229+
230 from canonical.testing.layers import DatabaseFunctionalLayer
231 from lp.code.enums import (
232 BranchSubscriptionDiffSize,
233@@ -259,8 +261,9 @@
234 mailer = BranchMailer.forRevision(
235 branch, 1, 'test@example.com', 'content', 'diff',
236 'Testing %j foo')
237+ branch_owner_email = removeSecurityProxy(branch.owner).preferredemail.email
238 self.assertEqual('Testing %j foo', mailer._getSubject(
239- branch.owner.preferredemail.email))
240+ branch_owner_email))
241
242
243 def test_suite():
244
245=== modified file 'lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py'
246--- lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py 2010-10-04 19:50:45 +0000
247+++ lib/lp/code/mail/tests/test_sourcepackagerecipebuild.py 2010-12-13 13:34:01 +0000
248@@ -48,7 +48,7 @@
249
250 def makeStatusEmail(self, build):
251 mailer = SourcePackageRecipeBuildMailer.forStatus(build)
252- email = build.requester.preferredemail.email
253+ email = removeSecurityProxy(build.requester).preferredemail.email
254 return mailer.generateEmail(email, build.requester)
255
256 def test_generateEmail(self):
257
258=== modified file 'lib/lp/code/model/recipebuilder.py'
259--- lib/lp/code/model/recipebuilder.py 2010-11-17 14:00:22 +0000
260+++ lib/lp/code/model/recipebuilder.py 2010-12-13 13:34:01 +0000
261@@ -12,6 +12,7 @@
262
263 from zope.component import adapts
264 from zope.interface import implements
265+from zope.security.proxy import removeSecurityProxy
266
267 from canonical.config import config
268 from lp.buildmaster.interfaces.builder import CannotBuild
269@@ -72,7 +73,11 @@
270 args["author_email"] = config.canonical.noreply_from_address
271 else:
272 args["author_name"] = requester.displayname
273- args["author_email"] = requester.preferredemail.email
274+ # We have to remove the security proxy here b/c there's not a
275+ # logged in entity, and anonymous email lookups aren't allowed.
276+ # Don't keep the naked requester around though.
277+ args["author_email"] = removeSecurityProxy(
278+ requester).preferredemail.email
279 args["recipe_text"] = str(self.build.recipe.builder_recipe)
280 args['archive_purpose'] = self.build.archive.purpose.name
281 args["ogrecomponent"] = get_primary_current_component(
282
283=== modified file 'lib/lp/code/model/tests/test_recipebuilder.py'
284--- lib/lp/code/model/tests/test_recipebuilder.py 2010-11-27 07:17:34 +0000
285+++ lib/lp/code/model/tests/test_recipebuilder.py 2010-12-13 13:34:01 +0000
286@@ -298,6 +298,7 @@
287 # dispatchBuildToSlave will fail when there is not chroot tarball
288 # available for the distroseries to build for.
289 job = self.makeJob()
290+ test_publisher = SoyuzTestPublisher()
291 builder = MockBuilder("bob-de-bouwer", OkSlave())
292 processorfamily = ProcessorFamilySet().getByProcessorName('386')
293 builder.processor = processorfamily.processors[0]
294
295=== modified file 'lib/lp/code/stories/feeds/xx-revision-atom.txt'
296--- lib/lp/code/stories/feeds/xx-revision-atom.txt 2010-10-18 22:24:59 +0000
297+++ lib/lp/code/stories/feeds/xx-revision-atom.txt 2010-12-13 13:34:01 +0000
298@@ -32,10 +32,11 @@
299 >>> fooey_branch = factory.makeProductBranch(
300 ... name='feature-x', product=fooey, owner=mike)
301
302+ >>> from zope.security.proxy import removeSecurityProxy
303 >>> def makeRevision(author, rev_id, log_body):
304 ... global factory, date_generator
305 ... return factory.makeRevision(
306- ... author=author.preferredemail.email,
307+ ... author=removeSecurityProxy(author).preferredemail.email,
308 ... revision_date=date_generator.next(),
309 ... rev_id=rev_id, log_body=log_body)
310 >>> ignored = fooey_branch.createBranchRevision(
311
312=== modified file 'lib/lp/code/tests/test_directbranchcommit.py'
313--- lib/lp/code/tests/test_directbranchcommit.py 2010-10-02 11:41:43 +0000
314+++ lib/lp/code/tests/test_directbranchcommit.py 2010-12-13 13:34:01 +0000
315@@ -5,6 +5,8 @@
316
317 __metaclass__ = type
318
319+from zope.security.proxy import removeSecurityProxy
320+
321 from canonical.testing.layers import (
322 DatabaseFunctionalLayer,
323 ZopelessDatabaseLayer,
324@@ -309,7 +311,8 @@
325 committer = DirectBranchCommit(branch)
326 self.addCleanup(committer.unlock)
327 self.assertIn(
328- branch.owner.preferredemail.email, committer.getBzrCommitterID())
329+ removeSecurityProxy(branch.owner).preferredemail.email,
330+ committer.getBzrCommitterID())
331
332 def test_falls_back_to_noreply(self):
333 # If all else fails, getBzrCommitterID uses the noreply
334
335=== modified file 'lib/lp/registry/browser/tests/person-views.txt'
336--- lib/lp/registry/browser/tests/person-views.txt 2010-11-01 03:46:17 +0000
337+++ lib/lp/registry/browser/tests/person-views.txt 2010-12-13 13:34:01 +0000
338@@ -106,14 +106,16 @@
339 can see them
340
341 Mark has a registered email address, and he has chosen to disclose it to
342-the world.
343+anyone in Launchpad..
344
345+ >>> login('test@canonical.com')
346 >>> mark = person_set.getByEmail('mark@example.com')
347 >>> mark.preferredemail.email
348 u'mark@example.com'
349
350 >>> mark.hide_email_addresses
351 False
352+ >>> logout()
353
354 Anonymous users cannot see any Launchpad user's email addresses. The
355 email addresses state is LOGIN_REQUIRED, there is no description, nor
356@@ -332,7 +334,7 @@
357
358 >>> from lp.blueprints.enums import SpecificationImplementationStatus
359
360- >>> login(user.preferredemail.email)
361+ >>> login_person(user)
362 >>> product = factory.makeProduct(name="tool", owner=user)
363 >>> spec = factory.makeSpecification(
364 ... product=product, title='Specs need stories')
365
366=== modified file 'lib/lp/registry/browser/tests/test_person_webservice.py'
367--- lib/lp/registry/browser/tests/test_person_webservice.py 2010-10-04 19:50:45 +0000
368+++ lib/lp/registry/browser/tests/test_person_webservice.py 2010-12-13 13:34:01 +0000
369@@ -5,12 +5,52 @@
370
371 import unittest
372
373+from zope.security.management import endInteraction
374 from zope.security.proxy import removeSecurityProxy
375
376 from canonical.launchpad.ftests import login
377 from canonical.launchpad.testing.pages import LaunchpadWebServiceCaller
378 from canonical.testing.layers import DatabaseFunctionalLayer
379-from lp.testing import TestCaseWithFactory
380+from lp.testing import (
381+ launchpadlib_for,
382+ launchpadlib_for_anonymous,
383+ TestCaseWithFactory,
384+ )
385+
386+
387+class TestPersonEmailSecurity(TestCaseWithFactory):
388+
389+ layer = DatabaseFunctionalLayer
390+
391+ def setUp(self):
392+ super(TestPersonEmailSecurity, self).setUp()
393+ self.target = self.factory.makePerson(name='target')
394+ self.email_one = self.factory.makeEmail(
395+ 'test1@example.com', self.target)
396+ self.email_two = self.factory.makeEmail(
397+ 'test2@example.com', self.target)
398+
399+ def test_logged_in_can_access(self):
400+ # A logged in launchpadlib connection can see confirmed email
401+ # addresses.
402+ accessor = self.factory.makePerson()
403+ lp = launchpadlib_for("test", accessor.name)
404+ person = lp.people['target']
405+ emails = sorted(list(person.confirmed_email_addresses))
406+ self.assertNotEqual(
407+ sorted([self.email_one, self.email_two]),
408+ len(emails))
409+
410+ def test_anonymous_cannot_access(self):
411+ # An anonymous launchpadlib connection cannot see email addresses.
412+
413+ # Need to endInteraction() because launchpadlib_for_anonymous() will
414+ # setup a new one.
415+ endInteraction()
416+ lp = launchpadlib_for_anonymous('test', version='devel')
417+ person = lp.people['target']
418+ emails = list(person.confirmed_email_addresses)
419+ self.assertEqual([], emails)
420
421
422 class TestPersonRepresentation(TestCaseWithFactory):
423
424=== modified file 'lib/lp/registry/browser/tests/user-to-user-views.txt'
425--- lib/lp/registry/browser/tests/user-to-user-views.txt 2010-08-17 15:57:37 +0000
426+++ lib/lp/registry/browser/tests/user-to-user-views.txt 2010-12-13 13:34:01 +0000
427@@ -24,7 +24,7 @@
428 ...No Priv would start by going to Salgado's +contactuser page.
429
430 >>> from canonical.launchpad.ftests import login
431- >>> login(no_priv.preferredemail.email)
432+ >>> login_person(no_priv)
433 >>> view = create_view(no_priv, salgado)
434
435 This contact is allowed.
436
437=== modified file 'lib/lp/registry/doc/message-holds.txt'
438--- lib/lp/registry/doc/message-holds.txt 2010-10-26 02:18:08 +0000
439+++ lib/lp/registry/doc/message-holds.txt 2010-12-13 13:34:01 +0000
440@@ -378,10 +378,10 @@
441
442
443 == Posting privileges ==
444-
445 Message approvals are also used to derived posting privileges for non-team
446 members. To start with, only the team owner can post to the list.
447
448+ >>> login('no-priv@canonical.com')
449 >>> team_three, list_three = mailinglists_helper.new_team(
450 ... 'test-three', True)
451 >>> sorted(email.email for email in list_three.getSenderAddresses())
452
453=== modified file 'lib/lp/registry/doc/person.txt'
454--- lib/lp/registry/doc/person.txt 2010-11-02 05:48:54 +0000
455+++ lib/lp/registry/doc/person.txt 2010-12-13 13:34:01 +0000
456@@ -639,6 +639,7 @@
457 If a team has a contact email address, all notifications we send to the
458 team will go to that address.
459
460+ >>> login('no-priv@canonical.com')
461 >>> ubuntu_team = personset.getByName('ubuntu-team')
462 >>> ubuntu_team.preferredemail.email
463 u'support@ubuntu.com'
464@@ -750,11 +751,11 @@
465 # First we'll define a utility function to help us displaying
466 # the results.
467
468- >>> emailset = getUtility(IEmailAddressSet)
469+ >>> naked_emailset = removeSecurityProxy(getUtility(IEmailAddressSet))
470 >>> def print_people(results):
471 ... for person in results:
472 ... emails = [email.email
473- ... for email in emailset.getByPerson(person)]
474+ ... for email in naked_emailset.getByPerson(person)]
475 ... print "%s (%s): %s" % (
476 ... person.displayname, person.name, emails)
477
478
479=== modified file 'lib/lp/registry/scripts/personnotification.py'
480--- lib/lp/registry/scripts/personnotification.py 2010-08-20 20:31:18 +0000
481+++ lib/lp/registry/scripts/personnotification.py 2010-12-13 13:34:01 +0000
482@@ -15,6 +15,7 @@
483
484 import pytz
485 from zope.component import getUtility
486+from zope.security.proxy import removeSecurityProxy
487
488 from canonical.config import config
489 from lp.registry.interfaces.personnotification import IPersonNotificationSet
490@@ -44,7 +45,7 @@
491 continue
492 self.logger.info(
493 "Sending notification to %s <%s>."
494- % (person.name, person.preferredemail.email))
495+ % (person.name, removeSecurityProxy(person).preferredemail.email))
496 notification.send()
497 notifications_sent = True
498 # Commit after each email sent, so that we won't re-mail the
499
500=== modified file 'lib/lp/registry/stories/webservice/xx-person.txt'
501--- lib/lp/registry/stories/webservice/xx-person.txt 2010-11-22 15:30:05 +0000
502+++ lib/lp/registry/stories/webservice/xx-person.txt 2010-12-13 13:34:01 +0000
503@@ -1,4 +1,5 @@
504-= People and teams =
505+People and teams
506+================
507
508 Since we use a single class (Person) to represent a person or a team,
509 representations of people and teams are supposed to have nearly the
510@@ -10,13 +11,16 @@
511 >>> pprint_entry(salgado)
512 admins_collection_link: u'http://.../~salgado/admins'
513 archive_link: None
514- confirmed_email_addresses_collection_link: u'http://.../~salgado/confirmed_email_addresses'
515+ confirmed_email_addresses_collection_link:
516+ u'http://.../~salgado/confirmed_email_addresses'
517 date_created: u'2005-06-06T08:59:51.596025+00:00'
518- deactivated_members_collection_link: u'http://.../~salgado/deactivated_members'
519+ deactivated_members_collection_link:
520+ u'http://.../~salgado/deactivated_members'
521 display_name: u'Guilherme Salgado'
522 expired_members_collection_link: u'http://.../~salgado/expired_members'
523 gpg_keys_collection_link: u'http://.../~salgado/gpg_keys'
524- hardware_submissions_collection_link: u'http://.../~salgado/hardware_submissions'
525+ hardware_submissions_collection_link:
526+ u'http://.../~salgado/hardware_submissions'
527 hide_email_addresses: False
528 homepage_content: None
529 invited_members_collection_link: u'http://.../~salgado/invited_members'
530@@ -34,13 +38,16 @@
531 mailing_list_auto_subscribe_policy: u'Ask me when I join a team'
532 members_collection_link: u'http://.../~salgado/members'
533 members_details_collection_link: u'http://.../~salgado/members_details'
534- memberships_details_collection_link: u'http://.../~salgado/memberships_details'
535+ memberships_details_collection_link:
536+ u'http://.../~salgado/memberships_details'
537 mugshot_link: u'http://.../~salgado/mugshot'
538 name: u'salgado'
539- open_membership_invitations_collection_link: u'http://.../~salgado/open_membership_invitations'
540+ open_membership_invitations_collection_link:
541+ u'http://.../~salgado/open_membership_invitations'
542 participants_collection_link: u'http://.../~salgado/participants'
543 ppas_collection_link: u'http://.../~salgado/ppas'
544- preferred_email_address_link: u'http://.../~salgado/+email/guilherme.salgado@canonical.com'
545+ preferred_email_address_link:
546+ u'http://.../~salgado/+email/guilherme.salgado@canonical.com'
547 private: False
548 proposed_members_collection_link: u'http://.../~salgado/proposed_members'
549 resource_type_link: u'http://.../#person'
550@@ -57,18 +64,23 @@
551 >>> pprint_entry(ubuntu_team)
552 admins_collection_link: u'http://.../~ubuntu-team/admins'
553 archive_link: None
554- confirmed_email_addresses_collection_link: u'http://.../~ubuntu-team/confirmed_email_addresses'
555+ confirmed_email_addresses_collection_link:
556+ u'http://.../~ubuntu-team/confirmed_email_addresses'
557 date_created: u'2005-06-06T08:59:51.605760+00:00'
558- deactivated_members_collection_link: u'http://.../~ubuntu-team/deactivated_members'
559+ deactivated_members_collection_link:
560+ u'http://.../~ubuntu-team/deactivated_members'
561 default_membership_period: None
562 default_renewal_period: None
563 display_name: u'Ubuntu Team'
564- expired_members_collection_link: u'http://.../~ubuntu-team/expired_members'
565+ expired_members_collection_link:
566+ u'http://.../~ubuntu-team/expired_members'
567 gpg_keys_collection_link: u'http://.../~ubuntu-team/gpg_keys'
568- hardware_submissions_collection_link: u'http://.../~ubuntu-team/hardware_submissions'
569+ hardware_submissions_collection_link:
570+ u'http://.../~ubuntu-team/hardware_submissions'
571 hide_email_addresses: False
572 homepage_content: None
573- invited_members_collection_link: u'http://.../~ubuntu-team/invited_members'
574+ invited_members_collection_link:
575+ u'http://.../~ubuntu-team/invited_members'
576 irc_nicknames_collection_link: u'http://.../~ubuntu-team/irc_nicknames'
577 is_probationary: False
578 is_team: True
579@@ -82,16 +94,21 @@
580 longitude: None
581 mailing_list_auto_subscribe_policy: u'Ask me when I join a team'
582 members_collection_link: u'http://.../~ubuntu-team/members'
583- members_details_collection_link: u'http://.../~ubuntu-team/members_details'
584- memberships_details_collection_link: u'http://.../~ubuntu-team/memberships_details'
585+ members_details_collection_link:
586+ u'http://.../~ubuntu-team/members_details'
587+ memberships_details_collection_link:
588+ u'http://.../~ubuntu-team/memberships_details'
589 mugshot_link: u'http://.../~ubuntu-team/mugshot'
590 name: u'ubuntu-team'
591- open_membership_invitations_collection_link: u'http://.../~ubuntu-team/open_membership_invitations'
592+ open_membership_invitations_collection_link:
593+ u'http://.../~ubuntu-team/open_membership_invitations'
594 participants_collection_link: u'http://.../~ubuntu-team/participants'
595 ppas_collection_link: u'http://.../~ubuntu-team/ppas'
596- preferred_email_address_link: u'http://.../~ubuntu-team/+email/support@ubuntu.com'
597+ preferred_email_address_link:
598+ u'http://.../~ubuntu-team/+email/support@ubuntu.com'
599 private: False
600- proposed_members_collection_link: u'http://.../~ubuntu-team/proposed_members'
601+ proposed_members_collection_link:
602+ u'http://.../~ubuntu-team/proposed_members'
603 renewal_policy: u'invite them to apply for renewal'
604 resource_type_link: u'http://.../#team'
605 self_link: u'http://.../~ubuntu-team'
606@@ -113,13 +130,15 @@
607 []
608
609
610-== Links to related things ==
611+Links to related things
612+-----------------------
613
614 As seen above, many attributes of a person are actually links to other
615 things (or collections).
616
617
618-=== Email addresses ===
619+Email addresses
620+...............
621
622 Apart from the link to the preferred email, there is a link to the
623 collection of other confirmed email addresses of that person/team.
624@@ -151,7 +170,8 @@
625 HTTP/1.1 404 Not Found
626 ...
627
628-== SSH keys ===
629+SSH keys
630+........
631
632 People have SSH keys which we can manipulate over the API.
633
634@@ -175,8 +195,8 @@
635 >>> ssh_key = factory.makeSSHKey(ssh_user)
636 >>> logout()
637
638-Now when we get the sshkey collection for 'sssh-user' again, the key should show
639-up:
640+Now when we get the sshkey collection for 'sssh-user' again, the key should
641+show up:
642
643 >>> keys = webservice.get(sshkeys).jsonBody()
644 >>> print_self_link_of_entries(keys)
645@@ -192,7 +212,8 @@
646 resource_type_link: u'http://.../#ssh_key'
647 self_link: u'http://.../~ssh-user/+ssh-keys/...'
648
649-=== GPG keys ===
650+GPG keys
651+........
652
653 People have GPG keys which we can manipulate over the API.
654
655@@ -230,7 +251,8 @@
656 self_link: u'http://.../~name12/+gpg-keys/...'
657
658
659-=== Team memberships ===
660+Team memberships
661+................
662
663 A person is linked to their team memberships.
664
665@@ -248,7 +270,8 @@
666 And to all membership invitations sent to it.
667
668 >>> lp_team = webservice.get("/~launchpad").jsonBody()
669- >>> lp_invitations = lp_team['open_membership_invitations_collection_link']
670+ >>> lp_invitations = lp_team[
671+ ... 'open_membership_invitations_collection_link']
672 >>> lp_invitations
673 u'http://.../~launchpad/open_membership_invitations'
674
675@@ -331,7 +354,7 @@
676 >>> print webservice.get(
677 ... salgado_landscape['self_link']).jsonBody()['status']
678 Deactivated
679-
680+
681 >>> print webservice.named_post(
682 ... salgado_landscape['self_link'], 'setStatus', {},
683 ... status='Approved', silent=True)
684@@ -341,7 +364,7 @@
685 >>> print webservice.get(
686 ... salgado_landscape['self_link']).jsonBody()['status']
687 Approved
688-
689+
690 >>> print webservice.named_post(
691 ... salgado_landscape['self_link'], 'setStatus', {},
692 ... status='Deactivated', silent=True)
693@@ -357,7 +380,8 @@
694 ...
695
696
697-=== Members ===
698+Members
699+.......
700
701 A list of team memberships is distinct from a list of a team's
702 members. Members are people; memberships are TeamMemberships. You've
703@@ -427,7 +451,8 @@
704 http://.../~karl
705
706
707-=== Sub-teams and super-teams ===
708+Sub-teams and super-teams
709+.........................
710
711 Teams can be members of other teams, and sometimes it's useful to know
712 which teams are members of any given team as well as the ones it is a
713@@ -442,7 +467,8 @@
714 http://.../~guadamen
715
716
717-=== Wiki names ===
718+Wiki names
719+..........
720
721 All wiki names associated to a person/team are also linked to that
722 person/team.
723@@ -461,7 +487,8 @@
724 Wiki names are first-class objects with their own URLs and
725 representations too.
726
727- >>> wiki_name = sorted(webservice.get(wikis_link).jsonBody()['entries'])[0]
728+ >>> wiki_name = sorted(
729+ ... webservice.get(wikis_link).jsonBody()['entries'])[0]
730 >>> pprint_entry(wiki_name)
731 person_link: u'http://.../~salgado'
732 resource_type_link: u'http://.../#wiki_name'
733@@ -485,7 +512,8 @@
734 ... u'wikiname': 'MrExample'}
735 >>> response = webservice.patch(
736 ... wiki_name['self_link'], 'application/json', dumps(patch))
737- >>> wiki_name = sorted(webservice.get(wikis_link).jsonBody()['entries'])[0]
738+ >>> wiki_name = sorted(
739+ ... webservice.get(wikis_link).jsonBody()['entries'])[0]
740 >>> print wiki_name['url']
741 http://www.example.com/MrExample
742
743@@ -503,7 +531,8 @@
744 Only URIs with the following schemes may be used: http, https
745
746
747-=== Jabber IDs ===
748+Jabber IDs
749+..........
750
751 Jabber IDs of a person are also linked.
752
753@@ -534,7 +563,8 @@
754 ...
755
756
757-=== IRC nicknames ===
758+IRC nicknames
759+.............
760
761 The same for IRC nicknames
762
763@@ -546,7 +576,8 @@
764
765 Anonymous listing is possible.
766
767- >>> print_self_link_of_entries(anon_webservice.get(irc_ids_link).jsonBody())
768+ >>> print_self_link_of_entries(
769+ ... anon_webservice.get(irc_ids_link).jsonBody())
770 http://.../~mark/+ircnick/1
771
772 IRC IDs are first-class objects with their own URLs and representations
773@@ -568,7 +599,8 @@
774 ...
775
776
777-=== PPAs ===
778+PPAs
779+....
780
781 We can get to the person's default PPA via the 'archive' property:
782
783@@ -634,7 +666,8 @@
784 [u'http://mark:testtoken@private-ppa.launchpad.dev/mark/p3a/ubuntu']
785
786
787-== Custom operations ==
788+Custom operations
789+-----------------
790
791 IPerson supports a bunch of operations.
792
793@@ -669,7 +702,8 @@
794 ---
795
796
797-=== Team membership operations ===
798+Team membership operations
799+..........................
800
801 Joining and leaving teams:
802
803@@ -791,14 +825,15 @@
804 # http://.../~mailing-list-experts
805
806
807-== Restrictions ==
808+Restrictions
809+------------
810
811 A team can't be its own owner.
812
813- >>> import simplejson
814- >>> doc = {'team_owner_link' : webservice.getAbsoluteUrl("/~admins")}
815- >>> print webservice.patch(
816- ... "/~admins", 'application/json', simplejson.dumps(doc))
817- HTTP/1.1 400 Bad Request
818- ...
819- team_owner_link: Constraint not satisfied.
820+ >>> import simplejson
821+ >>> doc = {'team_owner_link' : webservice.getAbsoluteUrl("/~admins")}
822+ >>> print webservice.patch(
823+ ... "/~admins", 'application/json', simplejson.dumps(doc))
824+ HTTP/1.1 400 Bad Request
825+ ...
826+ team_owner_link: Constraint not satisfied.
827
828=== modified file 'lib/lp/registry/tests/test_personset.py'
829--- lib/lp/registry/tests/test_personset.py 2010-10-04 19:50:45 +0000
830+++ lib/lp/registry/tests/test_personset.py 2010-12-13 13:34:01 +0000
831@@ -172,13 +172,13 @@
832 "when purchasing an application via Software Center.")
833
834 def test_existing_person(self):
835- person = self.factory.makePerson()
836+ email = 'test-email@example.com'
837+ person = self.factory.makePerson(email=email)
838 openid_ident = removeSecurityProxy(
839 person.account).openid_identifiers.any().identifier
840 person_set = getUtility(IPersonSet)
841
842- result, db_updated = self.callGetOrCreate(
843- openid_ident, email=person.preferredemail.email)
844+ result, db_updated = self.callGetOrCreate(openid_ident, email=email)
845
846 self.assertEqual(person, result)
847 self.assertFalse(db_updated)
848
849=== modified file 'lib/lp/registry/tests/test_product.py'
850--- lib/lp/registry/tests/test_product.py 2010-11-22 22:00:28 +0000
851+++ lib/lp/registry/tests/test_product.py 2010-12-13 13:34:01 +0000
852@@ -12,10 +12,7 @@
853 import transaction
854 from zope.component import getUtility
855
856-from canonical.launchpad.ftests import (
857- login,
858- syncUpdate,
859- )
860+from canonical.launchpad.ftests import syncUpdate
861 from canonical.launchpad.testing.pages import (
862 find_main_content,
863 get_feedback_messages,
864@@ -37,7 +34,11 @@
865 UnDeactivateable,
866 )
867 from lp.registry.model.productlicense import ProductLicense
868-from lp.testing import TestCaseWithFactory
869+from lp.testing import (
870+ login,
871+ login_person,
872+ TestCaseWithFactory,
873+ )
874
875
876 class TestProduct(TestCaseWithFactory):
877@@ -339,7 +340,7 @@
878 super(BugSupervisorTestCase, self).setUp()
879 self.person = self.factory.makePerson()
880 self.product = self.factory.makeProduct(owner=self.person)
881- login(self.person.preferredemail.email)
882+ login_person(self.person)
883
884 def testPersonCanSetSelfAsSupervisor(self):
885 # A person can set themselves as bug supervisor for a product.
886
887=== modified file 'lib/lp/registry/tests/test_project.py'
888--- lib/lp/registry/tests/test_project.py 2010-10-04 19:50:45 +0000
889+++ lib/lp/registry/tests/test_project.py 2010-12-13 13:34:01 +0000
890@@ -17,6 +17,7 @@
891 from lp.registry.interfaces.projectgroup import IProjectGroupSet
892 from lp.testing import (
893 launchpadlib_for,
894+ login_person,
895 TestCaseWithFactory,
896 )
897
898@@ -36,7 +37,7 @@
899 name="razzle-dazzle", owner=self.person,
900 description="Giving 110% at all times.")
901 self.projectset = getUtility(IProjectGroupSet)
902- login(self.person.preferredemail.email)
903+ login_person(self.person)
904
905 def testSearchNoMatch(self):
906 # Search for a string that does not exist.
907
908=== modified file 'lib/lp/services/mail/sendmail.py'
909--- lib/lp/services/mail/sendmail.py 2010-10-20 01:23:52 +0000
910+++ lib/lp/services/mail/sendmail.py 2010-12-13 13:34:01 +0000
911@@ -46,6 +46,7 @@
912 from lazr.restful.utils import get_current_browser_request
913 from zope.app import zapi
914 from zope.security.proxy import isinstance as zisinstance
915+from zope.security.proxy import removeSecurityProxy
916 from zope.sendmail.interfaces import IMailDelivery
917
918 from canonical.config import config
919@@ -155,7 +156,8 @@
920
921 def format_address_for_person(person):
922 """Helper function to call format_address for a person."""
923- return format_address(person.displayname, person.preferredemail.email)
924+ email_address = removeSecurityProxy(person.preferredemail).email
925+ return format_address(person.displayname, email_address)
926
927
928 def simple_sendmail(from_addr, to_addrs, subject, body, headers=None,
929
930=== modified file 'lib/lp/services/mailman/testing/__init__.py'
931--- lib/lp/services/mailman/testing/__init__.py 2010-10-06 11:46:51 +0000
932+++ lib/lp/services/mailman/testing/__init__.py 2010-12-13 13:34:01 +0000
933@@ -17,6 +17,7 @@
934 mm_cfg,
935 )
936 from Mailman.Queue import XMLRPCRunner
937+from zope.security.proxy import removeSecurityProxy
938
939 from canonical.testing.layers import DatabaseFunctionalLayer
940
941@@ -56,7 +57,7 @@
942 # This utility is based on mailman/tests/TestBase.py.
943 mlist = MailList.MailList()
944 team = lp_mailing_list.team
945- owner_email = team.teamowner.preferredemail.email
946+ owner_email = removeSecurityProxy(team.teamowner).preferredemail.email
947 mlist.Create(team.name, owner_email, 'password')
948 mlist.host_name = 'lists.launchpad.dev'
949 mlist.web_page_url = 'http://lists.launchpad.dev/mailman/'
950
951=== modified file 'lib/lp/services/mailman/tests/test_lpmoderate.py'
952--- lib/lp/services/mailman/tests/test_lpmoderate.py 2010-10-26 15:47:24 +0000
953+++ lib/lp/services/mailman/tests/test_lpmoderate.py 2010-12-13 13:34:01 +0000
954@@ -7,6 +7,7 @@
955
956 from Mailman import Errors
957 from Mailman.Handlers import LPModerate
958+from zope.security.proxy import removeSecurityProxy
959
960 from canonical.testing.layers import LaunchpadFunctionalLayer
961 from lp.services.mailman.testing import MailmanTestCase
962@@ -31,7 +32,7 @@
963 'team-1', 'team-1-owner')
964 self.mm_list = self.makeMailmanList(self.mailing_list)
965 self.lp_user = self.factory.makePerson()
966- self.lp_user_email = self.lp_user.preferredemail.email
967+ self.lp_user_email = removeSecurityProxy(self.lp_user).preferredemail.email
968
969 def tearDown(self):
970 super(TestLPModerateTestCase, self).tearDown()
971@@ -47,7 +48,8 @@
972
973 def test_process_message_from_subscriber(self):
974 # Messages from subscribers silently complete the process.
975- subscriber_email = self.team.teamowner.preferredemail.email
976+ subscriber_email = removeSecurityProxy(
977+ self.team.teamowner).preferredemail.email
978 message = self.makeMailmanMessage(
979 self.mm_list, subscriber_email, 'subject', 'any content.')
980 msg_data = {}
981
982=== modified file 'lib/lp/testing/factory.py'
983--- lib/lp/testing/factory.py 2010-12-09 11:03:53 +0000
984+++ lib/lp/testing/factory.py 2010-12-13 13:34:01 +0000
985@@ -1315,7 +1315,7 @@
986 if author is None:
987 author = self.getUniqueString('author')
988 elif IPerson.providedBy(author):
989- author = author.preferredemail.email
990+ author = removeSecurityProxy(author).preferredemail.email
991 if revision_date is None:
992 revision_date = datetime.now(pytz.UTC)
993 if parent_ids is None:
994@@ -1626,9 +1626,10 @@
995 mail = SignedMessage()
996 if email_address is None:
997 person = self.makePerson()
998- email_address = person.preferredemail.email
999+ email_address = removeSecurityProxy(person).preferredemail.email
1000 mail['From'] = email_address
1001- mail['To'] = self.makePerson().preferredemail.email
1002+ mail['To'] = removeSecurityProxy(
1003+ self.makePerson()).preferredemail.email
1004 if subject is None:
1005 subject = self.getUniqueString('subject')
1006 mail['Subject'] = subject
1007@@ -2716,7 +2717,7 @@
1008 if dsc_maintainer_rfc822 is None:
1009 dsc_maintainer_rfc822 = '%s <%s>' % (
1010 maintainer.displayname,
1011- maintainer.preferredemail.email)
1012+ removeSecurityProxy(maintainer).preferredemail.email)
1013
1014 if creator is None:
1015 creator = self.makePerson()
1016@@ -3105,7 +3106,7 @@
1017 msg['Message-Id'] = make_msgid('launchpad')
1018 msg['Date'] = formatdate()
1019 msg['To'] = to
1020- msg['From'] = sender.preferredemail.email
1021+ msg['From'] = removeSecurityProxy(sender).preferredemail.email
1022 msg['Subject'] = 'Sample'
1023
1024 if attachments is None:
1025@@ -3142,7 +3143,7 @@
1026 timezone=0)
1027 email = None
1028 if sender is not None:
1029- email = sender.preferredemail.email
1030+ email = removeSecurityProxy(sender).preferredemail.email
1031 return self.makeSignedMessage(
1032 body='My body', subject='My subject',
1033 attachment_contents=''.join(md.to_lines()),
1034
1035=== modified file 'lib/lp/testing/tests/test_login.py'
1036--- lib/lp/testing/tests/test_login.py 2010-10-26 15:47:24 +0000
1037+++ lib/lp/testing/tests/test_login.py 2010-12-13 13:34:01 +0000
1038@@ -122,8 +122,8 @@
1039
1040 def test_login_with_email(self):
1041 # login() logs a person in by email.
1042- person = self.factory.makePerson()
1043- email = person.preferredemail.email
1044+ email = 'test-email@example.com'
1045+ person = self.factory.makePerson(email=email)
1046 logout()
1047 login(email)
1048 self.assertLoggedIn(person)