Merge lp:~jcsackett/launchpad/anonymous-api-access-emails-681815 into lp:launchpad
- anonymous-api-access-emails-681815
- Merge into devel
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 |
Related bugs: |
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,
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 checkUnauthenti
Preimplementation talk
=======
Spoke with Curtis Hovey.
Implementation details
=======
As in Proposed.
Tests
=====
bin/test -t registry.
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
lib/lp/
./lib/canonical
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/
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.
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.
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): |
Edwin Grubbs (edwin-grubbs) wrote : | # |
Hi JC,
Thanks for adding tests that actually fail without your changes to checkUnauthenti
-Edwin
j.c.sackett (jcsackett) wrote : | # |
Edwin, Benji--
I've made the changes to make lint happy.
Thanks for the review, guys!
Preview Diff
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) |
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.