Merge lp:~edwin-grubbs/launchpad/bug-554153-claim-profile-bug into lp:launchpad

Proposed by Edwin Grubbs
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~edwin-grubbs/launchpad/bug-554153-claim-profile-bug
Merge into: lp:launchpad
Diff against target: 654 lines (+56/-377)
10 files modified
lib/canonical/launchpad/browser/logintoken.py (+4/-56)
lib/canonical/launchpad/doc/logintoken-pages.txt (+9/-77)
lib/canonical/launchpad/interfaces/authtoken.py (+0/-7)
lib/canonical/launchpad/zcml/logintoken.zcml (+0/-7)
lib/lp/registry/browser/configure.zcml (+0/-6)
lib/lp/registry/browser/person.py (+22/-62)
lib/lp/registry/stories/person/xx-person-claim-merge.txt (+17/-2)
lib/lp/registry/stories/person/xx-person-claim.txt (+0/-148)
lib/lp/registry/stories/team/xx-team-claim.txt (+4/-6)
lib/lp/registry/templates/person-index.pt (+0/-6)
To merge this branch: bzr merge lp:~edwin-grubbs/launchpad/bug-554153-claim-profile-bug
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Review via email: mp+23550@code.launchpad.net

Description of the change

Summary
-------

Fixes bug 554153.
Remove $person/+claim page and point links at /people/+requestmerge instead.

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

The +claim page emails the user a token that lets them access the
+claimprofile page. We can rid of them both.
    lib/canonical/launchpad/browser/logintoken.py
    lib/canonical/launchpad/zcml/logintoken.zcml
    lib/lp/registry/browser/configure.zcml

The +claimteam page is still used. It was subclassed from
PersonClaimView, but now those two views can be combined.
    lib/lp/registry/browser/person.py

There is no real reason to display a different set of links for an
anonymous user looking at an inactive person page. The anonymous
user will be forced to log in if the +requestmerge or +claimteam
links is clicked.
    lib/lp/registry/templates/person-index.pt

Tests:
    lib/lp/registry/stories/person/xx-person-claim-merge.txt
    lib/lp/registry/stories/person/xx-person-claim.txt
    lib/lp/registry/stories/team/xx-team-claim.txt

Tests
-----

./bin/test -vv -t claim

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

* Open http://launchpad.dev/~matsubara/+requestmerge
  * The "Are you Diogo Matsubara?" link should now point to +requestmerge.
* Open http://launchpad.dev/~doc/+requestmerge
  * The "Is this a team you run?" link should still point to +claimteam.

To post a comment you must log in.
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/logintoken.py'
2--- lib/canonical/launchpad/browser/logintoken.py 2010-04-01 19:22:36 +0000
3+++ lib/canonical/launchpad/browser/logintoken.py 2010-04-17 16:08:28 +0000
4@@ -6,7 +6,6 @@
5 __all__ = [
6 'BaseTokenView',
7 'BugTrackerHandshakeView',
8- 'ClaimProfileView',
9 'ClaimTeamView',
10 'LoginTokenSetNavigation',
11 'LoginTokenView',
12@@ -36,7 +35,6 @@
13 IGPGHandler)
14 from canonical.launchpad.interfaces.logintoken import (
15 IGPGKeyValidationForm, ILoginTokenSet)
16-from canonical.launchpad.interfaces.lpstorm import IMasterObject
17 from canonical.launchpad.webapp.interfaces import (
18 IAlwaysSubmittedWidget, IPlacelessLoginSource)
19 from canonical.launchpad.webapp.login import logInPrincipal
20@@ -45,10 +43,10 @@
21 from canonical.launchpad.webapp import (
22 action, canonical_url, custom_widget, GetitemNavigation,
23 LaunchpadEditFormView, LaunchpadFormView, LaunchpadView)
24-from canonical.widgets import LaunchpadRadioWidget, PasswordChangeWidget
25+from canonical.widgets import LaunchpadRadioWidget
26
27 from lp.registry.browser.team import HasRenewalPolicyMixin
28-from lp.registry.interfaces.person import IPerson, IPersonSet, ITeam
29+from lp.registry.interfaces.person import IPersonSet, ITeam
30
31
32 UTC = pytz.UTC
33@@ -77,7 +75,6 @@
34 LoginTokenType.VALIDATETEAMEMAIL: '+validateteamemail',
35 LoginTokenType.VALIDATEGPG: '+validategpg',
36 LoginTokenType.VALIDATESIGNONLYGPG: '+validatesignonlygpg',
37- LoginTokenType.PROFILECLAIM: '+claimprofile',
38 LoginTokenType.TEAMCLAIM: '+claimteam',
39 LoginTokenType.BUGTRACKER: '+bugtracker-handshake',
40 }
41@@ -184,56 +181,6 @@
42 return True
43
44
45-class ClaimProfileView(BaseTokenView, LaunchpadFormView):
46- schema = IPerson
47- field_names = ['displayname', 'hide_email_addresses', 'password']
48- custom_widget('password', PasswordChangeWidget)
49- label = 'Claim Launchpad profile'
50-
51- expected_token_types = (LoginTokenType.PROFILECLAIM,)
52-
53- def initialize(self):
54- if not self.redirectIfInvalidOrConsumedToken():
55- self.claimed_profile = getUtility(IEmailAddressSet).getByEmail(
56- self.context.email).person
57- super(ClaimProfileView, self).initialize()
58-
59- @property
60- def initial_values(self):
61- return {'displayname': self.claimed_profile.displayname}
62-
63- @property
64- def default_next_url(self):
65- return canonical_url(self.claimed_profile)
66-
67- @action(_('Continue'), name='confirm')
68- def confirm_action(self, action, data):
69- email = getUtility(IEmailAddressSet).getByEmail(self.context.email)
70- person = IMasterObject(email.person)
71-
72- # The user is not yet logged in, but we need to set some
73- # things on his new account, so we need to remove the security
74- # proxy from it.
75- # XXX: Guilherme Salgado 2006-09-27 bug=62674:
76- # We should be able to login with this person and set the
77- # password, to avoid removing the security proxy, but it didn't
78- # work, so I'm leaving this hack for now.
79- naked_person = removeSecurityProxy(person)
80- naked_person.displayname = data['displayname']
81- naked_person.hide_email_addresses = data['hide_email_addresses']
82-
83- naked_email = removeSecurityProxy(email)
84-
85- removeSecurityProxy(IMasterObject(email.account)).activate(
86- comment="Activated by claim profile.",
87- password=data['password'],
88- preferred_email=naked_email)
89- self.context.consume()
90- self.logInPrincipalByEmail(naked_email.email)
91- self.request.response.addInfoNotification(_(
92- "Profile claimed successfully"))
93-
94-
95 class ClaimTeamView(
96 BaseTokenView, HasRenewalPolicyMixin, LaunchpadEditFormView):
97
98@@ -356,7 +303,8 @@
99
100 # We compare the word-splitted content to avoid failures due
101 # to whitepace differences.
102- if signature.plain_data.split() != self.context.validation_phrase.split():
103+ if (signature.plain_data.split()
104+ != self.context.validation_phrase.split()):
105 self.addError(_(
106 'The signed content does not match the message found '
107 'in the email.'))
108
109=== modified file 'lib/canonical/launchpad/doc/logintoken-pages.txt'
110--- lib/canonical/launchpad/doc/logintoken-pages.txt 2010-04-01 18:48:04 +0000
111+++ lib/canonical/launchpad/doc/logintoken-pages.txt 2010-04-17 16:08:28 +0000
112@@ -6,11 +6,16 @@
113 token sent to him in an email.
114
115
116-== Claiming a profile ==
117+== Validating GPG keys ==
118
119-The ClaimProfileView allows users to claim accounts created by Launchpad
120-processes. The user profile for Matsubara was created during an import.
121-The user profile does not have a preferred email address or a password.
122+In order to add a GPG key to Launchpad we require that the person
123+registering it decrypt an email and follow the instructions in it.
124+The last step in that workflow is the +validategpg page, which can
125+be accessed by non-logged-in users. That page will attempt to access
126+all email addresses of the requester but that may fail when the user
127+is not logged in and the requester has chosen to hide his email
128+addresses, so here we'll make sure that even non-logged-in users can
129+perform the last step of the workflow for adding a GPG key.
130
131 >>> from zope.component import getMultiAdapter
132 >>> from canonical.launchpad.interfaces.authtoken import LoginTokenType
133@@ -25,79 +30,6 @@
134 >>> person_set = PersonSet()
135 >>> login_token_set = getUtility(ILoginTokenSet)
136
137- >>> matsubara = person_set.getByEmail('matsubara@async.com.br')
138- >>> matsubara.name
139- u'matsubara'
140- >>> matsubara.displayname
141- u'Diogo Matsubara'
142- >>> print matsubara.preferredemail
143- None
144-
145- >>> matsubara.account.status
146- <DBItem AccountStatus.NOACCOUNT, ...>
147- >>> print matsubara.account.password
148- None
149- >>> matsubara.is_valid_person
150- False
151-
152-The user can claim his profile by providing the email address that
153-is associated with the profile. If the correct address is provided,
154-the user is sent an email with a link to a LoginToken.
155-
156- >>> login_token = login_token_set.new(
157- ... matsubara, 'matsubara@async.com.br',
158- ... 'matsubara@async.com.br', LoginTokenType.PROFILECLAIM)
159-
160-The user is asked to provide a display name and password, and to
161-indicate if his email address can be shown to other Launchpad users.
162-
163- >>> request = LaunchpadTestRequest(
164- ... SERVER_URL='http://launchpad.dev',
165- ... PATH_INFO='/token/%s/+claimprofile' % login_token.token,
166- ... method='POST',
167- ... form={
168- ... 'field.displayname': 'D. Matsubara',
169- ... 'field.hide_email_addresses': 'on',
170- ... 'field.password': 'test4',
171- ... 'field.password_dupe': 'test4',
172- ... 'field.actions.confirm': 'Continue',
173- ... })
174- >>> login(ANONYMOUS, request)
175- >>> claimprofile_view = getMultiAdapter(
176- ... (login_token, request), name="+claimprofile")
177- >>> claimprofile_view.initialize()
178- >>> claimprofile_view.errors
179- []
180- >>> transaction.commit()
181-
182-The user's Person, EmailAddress, and Account objects are updated.
183-
184- >>> matsubara.displayname
185- u'D. Matsubara'
186- >>> matsubara.hide_email_addresses
187- True
188- >>> matsubara.account.preferredemail.email
189- u'matsubara@async.com.br'
190-
191- >>> matsubara.account.status
192- <DBItem AccountStatus.ACTIVE, ...>
193- >>> matsubara.account.status_comment
194- u'Activated by claim profile.'
195- >>> matsubara.is_valid_person
196- True
197-
198-
199-== Validating GPG keys ==
200-
201-In order to add a GPG key to Launchpad we require that the person
202-registering it decrypt an email and follow the instructions in it.
203-The last step in that workflow is the +validategpg page, which can
204-be accessed by non-logged-in users. That page will attempt to access
205-all email addresses of the requester but that may fail when the user
206-is not logged in and the requester has chosen to hide his email
207-addresses, so here we'll make sure that even non-logged-in users can
208-perform the last step of the workflow for adding a GPG key.
209-
210 >>> sample_person = person_set.getByEmail('test@canonical.com')
211 >>> sample_person.hide_email_addresses
212 True
213
214=== modified file 'lib/canonical/launchpad/interfaces/authtoken.py'
215--- lib/canonical/launchpad/interfaces/authtoken.py 2010-03-30 20:02:53 +0000
216+++ lib/canonical/launchpad/interfaces/authtoken.py 2010-04-17 16:08:28 +0000
217@@ -78,13 +78,6 @@
218 needs to be validated.
219 """)
220
221- PROFILECLAIM = DBItem(8, """
222- Claim an unvalidated Launchpad profile
223-
224- A user has found an unvalidated profile in Launchpad and is trying
225- to claim it.
226- """)
227-
228 NEWPROFILE = DBItem(9, """
229 A user created a new Launchpad profile for another person.
230
231
232=== modified file 'lib/canonical/launchpad/zcml/logintoken.zcml'
233--- lib/canonical/launchpad/zcml/logintoken.zcml 2010-03-30 20:02:53 +0000
234+++ lib/canonical/launchpad/zcml/logintoken.zcml 2010-04-17 16:08:28 +0000
235@@ -77,13 +77,6 @@
236
237 <browser:page
238 for="canonical.launchpad.interfaces.ILoginToken"
239- name="+claimprofile"
240- permission="zope.Public"
241- class="canonical.launchpad.browser.ClaimProfileView"
242- template="../templates/logintoken-claimprofile.pt" />
243-
244- <browser:page
245- for="canonical.launchpad.interfaces.ILoginToken"
246 name="+claimteam"
247 permission="launchpad.AnyPerson"
248 class="canonical.launchpad.browser.ClaimTeamView"
249
250=== modified file 'lib/lp/registry/browser/configure.zcml'
251--- lib/lp/registry/browser/configure.zcml 2010-04-12 15:33:19 +0000
252+++ lib/lp/registry/browser/configure.zcml 2010-04-17 16:08:28 +0000
253@@ -790,12 +790,6 @@
254 permission="launchpad.AnyPerson"
255 template="../templates/person-claim-team.pt"/>
256 <browser:page
257- name="+claim"
258- for="lp.registry.interfaces.person.IPerson"
259- class="lp.registry.browser.person.PersonClaimView"
260- permission="zope.Public"
261- template="../templates/person-claim.pt"/>
262- <browser:page
263 name="+editlanguages"
264 for="lp.registry.interfaces.person.IPerson"
265 class="lp.registry.browser.person.PersonLanguagesView"
266
267=== modified file 'lib/lp/registry/browser/person.py'
268--- lib/lp/registry/browser/person.py 2010-04-14 18:31:51 +0000
269+++ lib/lp/registry/browser/person.py 2010-04-17 16:08:28 +0000
270@@ -18,7 +18,6 @@
271 'PersonAssignedBugTaskSearchListingView',
272 'PersonBrandingView',
273 'PersonBugsMenu',
274- 'PersonClaimView',
275 'PersonCodeOfConductEditView',
276 'PersonCommentedBugTaskSearchListingView',
277 'PersonDeactivateAccountView',
278@@ -218,8 +217,7 @@
279 from canonical.launchpad.webapp.authorization import check_permission
280 from canonical.launchpad.webapp.batching import BatchNavigator
281 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
282-from canonical.launchpad.webapp.login import (
283- logoutPerson, allowUnauthenticatedSession)
284+from canonical.launchpad.webapp.login import logoutPerson
285 from canonical.launchpad.webapp.menu import get_current_view
286 from canonical.launchpad.webapp.publisher import LaunchpadView
287 from canonical.launchpad.webapp.tales import (
288@@ -1397,11 +1395,14 @@
289 self.next_url = self.request.getApplicationURL()
290
291
292-class PersonClaimView(LaunchpadFormView):
293- """The page where a user can claim an unvalidated profile."""
294+class BeginTeamClaimView(LaunchpadFormView):
295+ """Where you can claim an unvalidated profile turning it into a team.
296
297+ This is actually just the first step, where you enter the email address
298+ of the team and we email further instructions to that address.
299+ """
300+ label = 'Claim team'
301 schema = IPersonClaim
302- label = 'Claim account'
303
304 def initialize(self):
305 if self.context.is_valid_person_or_team:
306@@ -1431,28 +1432,17 @@
307 "associated with."
308 % self.context.name)
309 elif email.personID != self.context.id:
310- if email.person.is_valid_person:
311- error = structured(
312- "This email address is associated with yet another "
313- "Launchpad profile, which you seem to have used at "
314- "some point. If that's the case, you can "
315- '<a href="/people/+requestmerge'
316- '?field.dupe_person=%s">combine '
317- "this profile with the other one</a> (you'll "
318- "have to log in with the other profile first, "
319- "though). If that's not the case, please try with a "
320- "different email address.",
321- self.context.name)
322- else:
323- # There seems to be another unvalidated profile for you!
324- error = structured(
325- "Although this email address is not associated with "
326- "this profile, it's associated with yet another "
327- 'one. You can <a href="%s/+claim">claim that other '
328- 'profile</a> and then later '
329- '<a href="/people/+requestmerge">combine</a> both '
330- 'of them into a single one.',
331- canonical_url(email.person))
332+ error = structured(
333+ "This email address is associated with yet another "
334+ "Launchpad profile, which you seem to have used at "
335+ "some point. If that's the case, you can "
336+ '<a href="/people/+requestmerge'
337+ '?field.dupe_person=%s">combine '
338+ "this profile with the other one</a> (you'll "
339+ "have to log in with the other profile first, "
340+ "though). If that's not the case, please try with a "
341+ "different email address.",
342+ self.context.name)
343 else:
344 # Yay! You got the right email this time.
345 pass
346@@ -1463,34 +1453,6 @@
347 def next_url(self):
348 return canonical_url(self.context)
349
350- @action(_("E-mail Me"), name="confirm")
351- def confirm_action(self, action, data):
352- email = data['emailaddress']
353- token = getUtility(ILoginTokenSet).new(
354- requester=None, requesteremail=None, email=email,
355- tokentype=LoginTokenType.PROFILECLAIM)
356- token.sendClaimProfileEmail()
357- # A dance to assert that we want to break the rules about no
358- # unauthenticated sessions. Only after this next line is it safe
359- # to use the ``addNoticeNotification`` method.
360- allowUnauthenticatedSession(self.request)
361- self.request.response.addInfoNotification(_(
362- "A confirmation message has been sent to '${email}'. "
363- "Follow the instructions in that message to finish claiming this "
364- "profile. "
365- "(If the message doesn't arrive in a few minutes, your mail "
366- "provider might use 'greylisting', which could delay the message "
367- "for up to an hour or two.)", mapping=dict(email=email)))
368-
369-
370-class BeginTeamClaimView(PersonClaimView):
371- """Where you can claim an unvalidated profile turning it into a team.
372-
373- This is actually just the first step, where you enter the email address
374- of the team and we email further instructions to that address.
375- """
376- label = 'Claim team'
377-
378 @action(_("Continue"), name="confirm")
379 def confirm_action(self, action, data):
380 email = data['emailaddress']
381@@ -2398,9 +2360,9 @@
382 # The call to redeemVoucher returns True if it succeeds or it
383 # raises an exception. Therefore the return value does not need
384 # to be checked.
385- result = salesforce_proxy.redeemVoucher(voucher.voucher_id,
386- self.context,
387- project)
388+ salesforce_proxy.redeemVoucher(voucher.voucher_id,
389+ self.context,
390+ project)
391 project.redeemSubscriptionVoucher(
392 voucher=voucher.voucher_id,
393 registrant=self.context,
394@@ -3391,7 +3353,7 @@
395 'The URL scheme "%(scheme)s" is not allowed. '
396 'Only http or https URLs may be used.', scheme=uri.scheme)
397 return False
398- except InvalidURIError, e:
399+ except InvalidURIError:
400 self.error_message = structured(
401 '"%(url)s" is not a valid URL.', url=url)
402 return False
403@@ -4226,8 +4188,6 @@
404
405 @action(_("Leave"), name="leave")
406 def action_save(self, action, data):
407- response = self.request.response
408-
409 if self.user_can_request_to_leave:
410 self.user.leave(self.context)
411
412
413=== modified file 'lib/lp/registry/stories/person/xx-person-claim-merge.txt'
414--- lib/lp/registry/stories/person/xx-person-claim-merge.txt 2009-07-20 15:27:26 +0000
415+++ lib/lp/registry/stories/person/xx-person-claim-merge.txt 2010-04-17 16:08:28 +0000
416@@ -1,3 +1,20 @@
417+Claiming a person while not logged in
418+=====================================
419+
420+An anonymous user visiting an unclaimed account will be given the option
421+to request a merge, but the +requestmerge page will give an Unauthorized
422+exception that will redirect the user to the login page.
423+
424+ >>> anon_browser.open('http://launchpad.dev/~matsubara')
425+ >>> link = anon_browser.getLink("Are you Diogo Matsubara?")
426+ >>> print link.url
427+ http://launchpad.dev/people/+requestmerge?field.dupe_person=matsubara
428+ >>> link.click()
429+ Traceback (most recent call last):
430+ ...
431+ Unauthorized: ...
432+
433+
434 Claiming a person while logged in
435 =================================
436
437@@ -5,8 +22,6 @@
438 option to request a merge.
439
440 >>> user_browser.open('http://launchpad.dev/~matsubara')
441- >>> user_browser.url
442- 'http://launchpad.dev/~matsubara'
443 >>> user_browser.getLink("Are you Diogo Matsubara?").click()
444
445 Clicking on that link brought him to a page where he can request to
446
447=== removed file 'lib/lp/registry/stories/person/xx-person-claim.txt'
448--- lib/lp/registry/stories/person/xx-person-claim.txt 2009-09-16 17:56:17 +0000
449+++ lib/lp/registry/stories/person/xx-person-claim.txt 1970-01-01 00:00:00 +0000
450@@ -1,148 +0,0 @@
451-= Claiming a person =
452-
453-Matsubara was browsing through Launchpad anonymously and found a page
454-for himself, even though he has never used Launchpad (what a lie!).
455-
456- >>> browser.open('http://launchpad.dev/~matsubara')
457- >>> browser.url
458- 'http://launchpad.dev/~matsubara'
459-
460-He was wondering who created that when he saw a very prominent link,
461-saying 'Are you Diogo Matsubara?'. He promptly clicked on it.
462-
463- >>> print extract_text(
464- ... find_tag_by_id(browser.contents, 'not-lp-user-or-team'))
465- Diogo Matsubara does not use Launchpad. This page was created...
466- ...2006-12-13...when importing the Portuguese (Brazil) (pt_BR)...
467-
468- >>> browser.getLink("Are you Diogo Matsubara?").click()
469-
470-Clicking on that link brought him to a page where he can claim that
471-Launchpad profile.
472-
473- >>> browser.url
474- 'http://launchpad.dev/%7Ematsubara/+claim'
475-
476-He first tries it with an old email address he had, but we tell him that
477-the given email address is not associated with that profile.
478-
479- >>> browser.getControl('Email address').value = 'non@existent.org'
480- >>> browser.getControl('E-mail Me').click()
481- >>> soup = find_main_content(browser.contents)
482- >>> for tag in soup('div', 'message'):
483- ... print tag.renderContents()
484- We couldn't find this email address. Please try another one that could
485- possibly be associated with this profile. Note that this profile's name
486- (matsubara) was generated based on the email address it's associated with.
487-
488-Then he tries with Sample Person's email address.
489-
490- >>> browser.getControl('Email address').value = 'test@canonical.com'
491- >>> browser.getControl('E-mail Me').click()
492- >>> soup = find_main_content(browser.contents)
493- >>> for tag in soup('div', 'message'):
494- ... print tag.renderContents()
495- This email address is associated with yet another Launchpad profile, which
496- you seem to have used at some point. If that's the case, you can <a
497- href="/people/+requestmerge?field.dupe_person=matsubara">combine this
498- profile with the other one</a> (you'll have to log in with the other
499- profile first, though). If that's not the case, please try with a
500- different email address.
501-
502-And once again he tries with an email address he found in Launchpad, but
503-this one is associated with an unvalidated profile.
504-
505- >>> browser.getControl('Email address').value = 'morten@wtf.dk'
506- >>> browser.getControl('E-mail Me').click()
507- >>> soup = find_main_content(browser.contents)
508- >>> for tag in soup('div', 'message'):
509- ... print tag.renderContents()
510- Although this email address is not associated with this profile, it's
511- associated with yet another one. You can <a
512- href="http://launchpad.dev/~morten/+claim">claim that other
513- profile</a> and then later <a href="/people/+requestmerge">combine</a>
514- both of them into a single one.
515-
516-Now he finally remembered his email address.
517-
518- >>> browser.getControl('Email address').value = 'matsubara@async.com.br'
519- >>> browser.getControl('E-mail Me').click()
520-
521- >>> from lp.services.mail import stub
522- >>> from canonical.launchpad.ftests.logintoken import (
523- ... get_token_url_from_email)
524- >>> from_addr, to_addr, msg = stub.test_emails.pop()
525- >>> to_addr
526- ['matsubara@async.com.br']
527- >>> token_url = get_token_url_from_email(msg)
528- >>> token_url
529- 'http://launchpad.dev/token/...'
530-
531- >>> browser.open(token_url)
532- >>> browser.url == "%s/+claimprofile" % token_url
533- True
534-
535- >>> browser.getControl(name='field.password').value = 'foo'
536- >>> browser.getControl(name='field.password_dupe').value = 'foo'
537- >>> browser.getControl('Continue').click()
538-
539- >>> browser.url
540- 'http://launchpad.dev/~matsubara'
541- >>> soup = find_main_content(browser.contents)
542- >>> for tag in soup('div', 'informational message'):
543- ... print tag.renderContents()
544- Profile claimed successfully
545-
546-It's also possible that a person tries to claim an account that has a
547-preferred email but no password, like some accounts imported from
548-Ubuntu's bugzilla. Although these accounts are not considered valid
549-(that's why they can be claimed) they /do/ have a preferred email and we
550-need to handle this.
551-
552- # To see kiko's email we must be logged in, so we'll use user_browser.
553- >>> user_browser.open('http://launchpad.dev/~kiko')
554- >>> unregistered_user_info = find_tag_by_id(
555- ... user_browser.contents, 'not-lp-user-or-team')
556- >>> unregistered_user_info.li.renderContents()
557- '...christian.reis@ubuntulinux.com...'
558- >>> unregistered_user_info.a.renderContents()
559- 'Are you Christian Reis?'
560-
561- >>> anon_browser.open('http://launchpad.dev/~kiko')
562- >>> anon_browser.getLink("Are you Christian Reis?").click()
563- >>> anon_browser.url
564- 'http://launchpad.dev/%7Ekiko/+claim'
565-
566- >>> anon_browser.getControl('Email address').value = (
567- ... 'christian.reis@ubuntulinux.com')
568- >>> anon_browser.getControl('E-mail Me').click()
569-
570- >>> from_addr, to_addr, msg = stub.test_emails.pop()
571- >>> to_addr
572- ['christian.reis@ubuntulinux.com']
573- >>> token_url = get_token_url_from_email(msg)
574- >>> token_url
575- 'http://launchpad.dev/token/...'
576-
577- >>> anon_browser.open(token_url)
578- >>> anon_browser.url == "%s/+claimprofile" % token_url
579- True
580-
581- >>> anon_browser.getControl(name='field.password').value = 'foo'
582- >>> anon_browser.getControl(name='field.password_dupe').value = 'foo'
583- >>> anon_browser.getControl('Continue').click()
584-
585- >>> anon_browser.url
586- 'http://launchpad.dev/~kiko'
587- >>> soup = find_main_content(anon_browser.contents)
588- >>> for tag in soup('div', 'informational message'):
589- ... print tag.renderContents()
590- Profile claimed successfully
591-
592-Trying to access +claim for an already active team 404s as you would
593-expect it to (we shouldn't be linking to from anywhere):
594-
595- >>> user_browser.open("http://launchpad.dev/~ubuntu-team/+claim")
596- Traceback (most recent call last):
597- ...
598- NotFound:...
599
600=== modified file 'lib/lp/registry/stories/team/xx-team-claim.txt'
601--- lib/lp/registry/stories/team/xx-team-claim.txt 2009-11-22 15:43:16 +0000
602+++ lib/lp/registry/stories/team/xx-team-claim.txt 2010-04-17 16:08:28 +0000
603@@ -14,7 +14,7 @@
604 definitely doesn't seem to be a team.
605
606 >>> browser.open('http://launchpad.dev/~matsubara')
607- >>> browser.getLink(url='+claim')
608+ >>> browser.getLink(url='+requestmerge')
609 <Link text='Are you Diogo Matsubara?'...
610
611 If a profile does look like a team and there is a logged in user, the link
612@@ -25,15 +25,13 @@
613 >>> [email.email for email in doc_team.guessedemails]
614 [u'doc@lists.ubuntu.com']
615
616-Non-logged-in users see only a link to claim the profile..
617+Non-logged-in users must log in to claim a team.
618
619 >>> browser.open('http://launchpad.dev/~doc')
620- >>> browser.getLink(url='+claimteam')
621+ >>> browser.getLink(url='+claimteam').click()
622 Traceback (most recent call last):
623 ...
624- LinkNotFoundError
625- >>> browser.getLink(url='+claim')
626- <Link text='Are you Ubuntu Doc Team?'...
627+ Unauthorized:...
628
629 While logged-in users get a link to claim that profile and turn it into a
630 team. This works even if the profile's email addresses would normally be
631
632=== modified file 'lib/lp/registry/templates/person-index.pt'
633--- lib/lp/registry/templates/person-index.pt 2009-12-12 15:21:34 +0000
634+++ lib/lp/registry/templates/person-index.pt 2010-04-17 16:08:28 +0000
635@@ -157,7 +157,6 @@
636 </tal:block>
637 </p>
638
639- <tal:logged-in condition="request/lp:person">
640 <tal:person condition="not: view/context_is_probably_a_team">
641 <ul tal:condition="context/preferredemail">
642 <li>
643@@ -183,11 +182,6 @@
644 <tal:team condition="view/context_is_probably_a_team">
645 <a href="+claimteam">Is this a team you run?</a>
646 </tal:team>
647- </tal:logged-in>
648-
649- <tal:not-logged-in condition="not: request/lp:person">
650- <a href="+claim">Are you <span tal:replace="context/displayname" />?</a>
651- </tal:not-logged-in>
652 </tal:noaccount>
653
654 <tal:deactivated-account