Merge lp:~salgado/launchpad/remove-logintoken-unused-code into lp:launchpad

Proposed by Guilherme Salgado
Status: Merged
Merged at revision: not available
Proposed branch: lp:~salgado/launchpad/remove-logintoken-unused-code
Merge into: lp:launchpad
Prerequisite: lp:~salgado/launchpad/remove-auth-store
Diff against target: 1474 lines (+43/-1077)
23 files modified
lib/canonical/launchpad/browser/ftests/logintoken-corner-cases.txt (+10/-11)
lib/canonical/launchpad/browser/logintoken.py (+7/-228)
lib/canonical/launchpad/browser/tests/test_logintoken.py (+1/-7)
lib/canonical/launchpad/browser/tests/test_password_reset.py (+0/-94)
lib/canonical/launchpad/database/logintoken.py (+0/-17)
lib/canonical/launchpad/doc/logintoken-pages.txt (+13/-295)
lib/canonical/launchpad/emailtemplates/forgottenpassword-neutral.txt (+0/-14)
lib/canonical/launchpad/emailtemplates/forgottenpassword.txt (+0/-11)
lib/canonical/launchpad/emailtemplates/newuser-email-neutral.txt (+0/-15)
lib/canonical/launchpad/emailtemplates/newuser-email.txt (+0/-19)
lib/canonical/launchpad/interfaces/authtoken.py (+3/-53)
lib/canonical/launchpad/templates/logintoken-newaccount.pt (+0/-19)
lib/canonical/launchpad/templates/logintoken-resetpassword.pt (+0/-18)
lib/canonical/launchpad/tests/test_login.py (+1/-20)
lib/canonical/launchpad/tests/test_token_creation.py (+2/-2)
lib/canonical/launchpad/webapp/login.py (+2/-20)
lib/canonical/launchpad/webapp/publication.py (+1/-1)
lib/canonical/launchpad/zcml/logintoken.zcml (+0/-16)
lib/lp/registry/browser/configure.zcml (+0/-6)
lib/lp/registry/browser/person.py (+2/-32)
lib/lp/registry/interfaces/person.py (+1/-40)
lib/lp/registry/stories/person/xx-new-profile.txt (+0/-110)
lib/lp/registry/templates/people-newperson.pt (+0/-29)
To merge this branch: bzr merge lp:~salgado/launchpad/remove-logintoken-unused-code
Reviewer Review Type Date Requested Status
Jonathan Lange (community) Approve
Review via email: mp+22719@code.launchpad.net

Description of the change

Remove views (and logintoken code) for registering new accounts and resetting passwords as this is now handled by the OpenID provider.

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Rubber-stamp. Haven't actually examined the diff.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/ftests/logintoken-corner-cases.txt'
2--- lib/canonical/launchpad/browser/ftests/logintoken-corner-cases.txt 2009-07-17 15:26:05 +0000
3+++ lib/canonical/launchpad/browser/ftests/logintoken-corner-cases.txt 2010-04-05 17:13:29 +0000
4@@ -7,25 +7,24 @@
5
6 === Double Post on the NewAccountView ===
7
8-Using the +newaccount view on a token that was already confirmed should
9+Using the +validateemail view on a token that was already consumed should
10 redirect to the default token view. This would happen if for example the
11-user tried to re-post the form after registering his or her account.
12+user tried to re-post the form after validating one of their email addresses.
13
14+ >>> from lp.registry.interfaces.person import IPersonSet
15 >>> from canonical.launchpad.interfaces import (
16 ... ILoginTokenSet, LoginTokenType)
17- >>> from canonical.launchpad.browser import NewUserAccountView
18+ >>> from canonical.launchpad.browser import ValidateEmailView
19 >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
20
21+ >>> foo_bar = getUtility(IPersonSet).getByName('name16')
22 >>> token = getUtility(ILoginTokenSet).new(
23- ... requester=None, requesteremail=None,
24- ... email='foo@barino.com', tokentype=LoginTokenType.NEWACCOUNT)
25+ ... requester=foo_bar, requesteremail='foo.bar@canonical.com',
26+ ... email='foo@barino.com', tokentype=LoginTokenType.VALIDATEEMAIL)
27 >>> token.consume()
28- >>> form = {'field.actions.continue': 'Continue',
29- ... 'field.displayname': 'Alex',
30- ... 'field.hide_email_addresses': 'off',
31- ... 'field.password': '23-is-the-answer',
32- ... 'field.password_dupe': '23-is-the-answer'}
33- >>> view = NewUserAccountView(token, LaunchpadTestRequest(form=form))
34+ >>> form = {'field.actions.continue': 'Continue'}
35+ >>> view = ValidateEmailView(
36+ ... token, LaunchpadTestRequest(form=form, method='POST'))
37 >>> view.initialize()
38
39 >>> response = view.request.response
40
41=== modified file 'lib/canonical/launchpad/browser/logintoken.py'
42--- lib/canonical/launchpad/browser/logintoken.py 2010-03-12 19:56:06 +0000
43+++ lib/canonical/launchpad/browser/logintoken.py 2010-04-05 17:13:29 +0000
44@@ -11,8 +11,6 @@
45 'LoginTokenSetNavigation',
46 'LoginTokenView',
47 'MergePeopleView',
48- 'NewUserAccountView',
49- 'ResetPasswordView',
50 'ValidateEmailView',
51 'ValidateTeamEmailView',
52 'ValidateGPGKeyView',
53@@ -24,16 +22,13 @@
54
55 from zope.app.form.browser import TextAreaWidget
56 from zope.component import getUtility
57-from zope.event import notify
58 from zope.interface import alsoProvides, directlyProvides, Interface
59-from zope.lifecycleevent import ObjectCreatedEvent
60 from zope.security.proxy import removeSecurityProxy
61
62 from canonical.database.sqlbase import flush_database_updates
63 from canonical.launchpad import _
64-from canonical.launchpad.interfaces.account import AccountStatus, IAccountSet
65-from canonical.launchpad.interfaces.authtoken import (
66- IAuthToken, LoginTokenType)
67+from canonical.launchpad.interfaces.account import AccountStatus
68+from canonical.launchpad.interfaces.authtoken import LoginTokenType
69 from canonical.launchpad.interfaces.emailaddress import (
70 EmailAddressStatus, IEmailAddressSet)
71 from canonical.launchpad.interfaces.gpghandler import (
72@@ -53,8 +48,7 @@
73 from canonical.widgets import LaunchpadRadioWidget, PasswordChangeWidget
74
75 from lp.registry.browser.team import HasRenewalPolicyMixin
76-from lp.registry.interfaces.person import (
77- INewPersonForm, IPerson, IPersonSet, ITeam, PersonCreationRationale)
78+from lp.registry.interfaces.person import IPerson, IPersonSet, ITeam
79
80
81 UTC = pytz.UTC
82@@ -77,15 +71,9 @@
83 they got this token because they tried to do something that required email
84 address confirmation, but that confirmation is already concluded.
85 """
86- auth_token_pages = {
87- LoginTokenType.NEWPERSONLESSACCOUNT: '+newaccount',
88- LoginTokenType.PASSWORDRECOVERY: '+resetpassword',
89+ PAGES = {
90+ LoginTokenType.ACCOUNTMERGE: '+accountmerge',
91 LoginTokenType.VALIDATEEMAIL: '+validateemail',
92- }
93- login_token_pages = {
94- LoginTokenType.NEWPROFILE: '+newaccount',
95- LoginTokenType.NEWACCOUNT: '+newaccount',
96- LoginTokenType.ACCOUNTMERGE: '+accountmerge',
97 LoginTokenType.VALIDATETEAMEMAIL: '+validateteamemail',
98 LoginTokenType.VALIDATEGPG: '+validategpg',
99 LoginTokenType.VALIDATESIGNONLYGPG: '+validatesignonlygpg',
100@@ -93,8 +81,6 @@
101 LoginTokenType.TEAMCLAIM: '+claimteam',
102 LoginTokenType.BUGTRACKER: '+bugtracker-handshake',
103 }
104- login_token_pages.update(auth_token_pages)
105- PAGES = login_token_pages
106 page_title = 'You have already done this'
107 label = 'Confirmation already concluded'
108
109@@ -198,81 +184,6 @@
110 return True
111
112
113-class ResetPasswordView(BaseTokenView, LaunchpadFormView):
114-
115- schema = IAuthToken
116- field_names = ['email', 'password']
117- custom_widget('password', PasswordChangeWidget)
118- label = 'Reset password'
119- expected_token_types = (LoginTokenType.PASSWORDRECOVERY,)
120-
121- def initialize(self):
122- self.redirectIfInvalidOrConsumedToken()
123- super(ResetPasswordView, self).initialize()
124-
125- def validate(self, form_values):
126- """Validate the email address."""
127- email = form_values.get("email", "").strip()
128- # All operations with email addresses must be case-insensitive. We
129- # enforce that in EmailAddressSet, but here we only do a comparison,
130- # so we have to .lower() them first.
131- if email.lower() != self.context.email.lower():
132- self.addError(_(
133- "The email address you provided didn't match the address "
134- "you provided when requesting the password reset."))
135-
136- @property
137- def default_next_url(self):
138- if self.context.redirection_url is not None:
139- return self.context.redirection_url
140- else:
141- return self.request.getApplicationURL()
142-
143- @action(_('Continue'), name='continue')
144- def continue_action(self, action, data):
145- """Reset the user's password. When password is successfully changed,
146- the AuthToken (self.context) used is consumed, so nobody can use
147- it again.
148- """
149- account = self.context.requester_account
150- # Suspended accounts cannot reset their password.
151- reason = ('Your password cannot be reset because your account '
152- 'is suspended.')
153- if self.accountWasSuspended(account, reason):
154- return
155-
156- naked_account = removeSecurityProxy(account)
157- # Reset password can be used to reactivate a deactivated account.
158- inactive_states = [AccountStatus.DEACTIVATED, AccountStatus.NOACCOUNT]
159- if account.status in inactive_states:
160- self.reactivate(data)
161- self.request.response.addInfoNotification(
162- _('Welcome back to Launchpad.'))
163- else:
164- naked_account.password = data.get('password')
165-
166- self.context.consume()
167- self.logInPrincipalByEmail(self.context.email)
168-
169- self.request.response.addInfoNotification(
170- _('Your password has been reset successfully.'))
171-
172- @action(_('Cancel'), name='cancel', validator='validate_cancel')
173- def cancel_action(self, action, data):
174- self._cancel()
175-
176- def reactivate(self, data):
177- """Reactivate the person (and account) of this token."""
178- emailaddress = getUtility(IEmailAddressSet).getByEmail(
179- self.context.email)
180- # Need to remove the security proxy of the account because at this
181- # point the user is not logged in.
182- removeSecurityProxy(self.context.requester).reactivate(
183- comment="User reactivated the account using reset password.",
184- password=data['password'],
185- preferred_email=emailaddress)
186-
187-
188 class ClaimProfileView(BaseTokenView, LaunchpadFormView):
189 schema = IPerson
190 field_names = ['displayname', 'hide_email_addresses', 'password']
191@@ -545,7 +456,8 @@
192 label = 'Confirm e-mail address'
193
194 def initialize(self):
195- self.redirectIfInvalidOrConsumedToken()
196+ if self.redirectIfInvalidOrConsumedToken():
197+ return
198 super(ValidateEmailView, self).initialize()
199
200 def validate(self, data):
201@@ -746,136 +658,3 @@
202 self.request.response.setStatus(200)
203 self.request.response.setHeader('Content-type', 'text/plain')
204 return "Handshake token validated."
205-
206-
207-class NewUserAccountView(BaseTokenView, LaunchpadFormView):
208- """Page to create a new Launchpad user account."""
209-
210- created_person = None
211-
212- schema = INewPersonForm
213- field_names = ['displayname', 'hide_email_addresses', 'password']
214- custom_widget('password', PasswordChangeWidget)
215- label = 'Complete your registration'
216- expected_token_types = (
217- LoginTokenType.NEWACCOUNT, LoginTokenType.NEWPROFILE)
218-
219- def initialize(self):
220- if self.redirectIfInvalidOrConsumedToken():
221- return
222- else:
223- self.email = getUtility(IEmailAddressSet).getByEmail(
224- self.context.email)
225- super(NewUserAccountView, self).initialize()
226-
227- @property
228- def default_next_url(self):
229- if self.context.redirection_url:
230- return self.context.redirection_url
231- elif self.created_person is not None:
232- return canonical_url(self.created_person)
233- else:
234- return None
235-
236- def validate(self, form_values):
237- """Verify if the email address is not used by an existing account."""
238- if self.email is not None:
239- if self.email.person is not None:
240- person = IMasterObject(self.email.person)
241- if person.is_valid_person:
242- self.addError(_(
243- 'The email address ${email} is already registered.',
244- mapping=dict(email=self.context.email)))
245- else:
246- self.addError(_(
247- 'The email address ${email} is already registered in '
248- 'the Launchpad Login Service (used by the Ubuntu shop '
249- 'and other OpenID sites). Please use the same email and '
250- 'password to log into Launchpad.',
251- mapping=dict(email=self.context.email)))
252-
253-
254- @action(_('Continue'), name='continue')
255- def continue_action(self, action, data):
256- """Create a new Person with the context's email address and set a
257- preferred email and password to it, or use an existing Person
258- associated with the context's email address, setting it as the
259- preferred address and also setting the password.
260-
261- If everything went ok, we consume the LoginToken (self.context), so
262- nobody can use it again.
263- """
264- if self.email is not None:
265- assert self.email.person is not None, (
266- "People trying to register using emails associated with "
267- "personless accounts should be told to just use their Login "
268- "Service credentials to log into LP")
269- # This is a placeholder profile automatically created by one of
270- # our scripts, let's just confirm its email address and set a
271- # password.
272- person = getUtility(IPersonSet).get(
273- removeSecurityProxy(self.email).personID)
274- assert not person.is_valid_person, (
275- 'Account %s has already been claimed and this should '
276- 'have been caught by the validate() method.' % person.name)
277- email = self.email
278- # The user is not yet logged in, but we need to set some
279- # things on his new account, so we need to remove the security
280- # proxy from it.
281- # XXX: Guilherme Salgado 2006-09-27 bug=62674:
282- # We should be able to login with this person and set the
283- # password, to avoid removing the security proxy, but it didn't
284- # work, so I'm leaving this hack for now.
285- naked_person = removeSecurityProxy(person)
286- # Suspended accounts cannot reactivate their profile.
287- reason = ('This profile cannot be claimed because the account '
288- 'is suspended.')
289- if self.accountWasSuspended(person.account, reason):
290- return
291- naked_person.displayname = data['displayname']
292- naked_person.hide_email_addresses = data['hide_email_addresses']
293- # Need to remove the security proxy of the account because at this
294- # point the user is not logged in.
295- account = removeSecurityProxy(IMasterObject(person.account))
296- account.activate(
297- "Activated by new account.",
298- password=data['password'],
299- preferred_email=self.email)
300- naked_person.creation_rationale = self._getCreationRationale()
301- naked_person.creation_comment = None
302- else:
303- account, person, email = self._createAccountEmailAndMaybePerson(
304- data['displayname'], data['hide_email_addresses'],
305- data['password'])
306-
307- self.context.consume()
308- self.logInPrincipalByEmail(removeSecurityProxy(email).email)
309- self.created_person = person
310- self.request.response.addInfoNotification(_(
311- "Registration completed successfully"))
312-
313- def _getCreationRationale(self):
314- """The creation rationale that should be used for this account."""
315- return PersonCreationRationale.OWNER_CREATED_LAUNCHPAD
316-
317- def _createAccountEmailAndMaybePerson(
318- self, displayname, hide_email_addresses, password):
319- """Create and return a new Account, Person and EmailAddress.
320-
321- This method will always create an Account (in the ACTIVE state),
322- an EmailAddress as the account's preferred one and a Person.
323-
324- Also fire ObjectCreatedEvents for both the newly created Account,
325- EmailAddress, and Person.
326- """
327- person, email = getUtility(IPersonSet).createPersonAndEmail(
328- self.context.email, rationale=self._getCreationRationale(),
329- displayname=displayname, password=password,
330- passwordEncrypted=True, hide_email_addresses=hide_email_addresses)
331- person.validateAndEnsurePreferredEmail(email)
332- account = getUtility(IAccountSet).get(person.accountID)
333- removeSecurityProxy(account).status = AccountStatus.ACTIVE
334- notify(ObjectCreatedEvent(person))
335- notify(ObjectCreatedEvent(account))
336- notify(ObjectCreatedEvent(email))
337- return account, person, email
338
339=== modified file 'lib/canonical/launchpad/browser/tests/test_logintoken.py'
340--- lib/canonical/launchpad/browser/tests/test_logintoken.py 2009-11-03 17:13:21 +0000
341+++ lib/canonical/launchpad/browser/tests/test_logintoken.py 2010-04-05 17:13:29 +0000
342@@ -6,7 +6,7 @@
343 from zope.component import getUtility
344
345 from canonical.launchpad.browser.logintoken import (
346- ClaimTeamView, ResetPasswordView, ValidateEmailView, ValidateGPGKeyView)
347+ ClaimTeamView, ValidateEmailView, ValidateGPGKeyView)
348 from canonical.launchpad.ftests import LaunchpadFormHarness
349 from canonical.launchpad.interfaces.authtoken import LoginTokenType
350 from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
351@@ -29,12 +29,6 @@
352 self.email = self.person.preferredemail.email
353 self.expected_next_url = 'http://127.0.0.1/~test-user'
354
355- def test_ResetPasswordView(self):
356- token = getUtility(ILoginTokenSet).new(
357- self.person, self.email, self.email,
358- LoginTokenType.PASSWORDRECOVERY)
359- self._testCancelAction(ResetPasswordView, token)
360-
361 def test_ClaimTeamView(self):
362 token = getUtility(ILoginTokenSet).new(
363 self.person, self.email, self.email, LoginTokenType.TEAMCLAIM)
364
365=== removed file 'lib/canonical/launchpad/browser/tests/test_password_reset.py'
366--- lib/canonical/launchpad/browser/tests/test_password_reset.py 2010-03-11 20:54:36 +0000
367+++ lib/canonical/launchpad/browser/tests/test_password_reset.py 1970-01-01 00:00:00 +0000
368@@ -1,94 +0,0 @@
369-# Copyright 2009 Canonical Ltd. This software is licensed under the
370-# GNU Affero General Public License version 3 (see the file LICENSE).
371-
372-import unittest
373-
374-from zope.component import getUtility
375-from zope.security.proxy import removeSecurityProxy
376-
377-import transaction
378-from canonical.launchpad.browser.logintoken import ResetPasswordView
379-from canonical.launchpad.ftests import LaunchpadFormHarness
380-from canonical.launchpad.interfaces.account import AccountStatus
381-from canonical.launchpad.interfaces.authtoken import LoginTokenType
382-from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
383-from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
384-from canonical.launchpad.interfaces.lpstorm import IMasterObject
385-from lp.testing import TestCaseWithFactory
386-from canonical.testing import DatabaseFunctionalLayer
387-
388-
389-class TestPasswordReset(TestCaseWithFactory):
390- layer = DatabaseFunctionalLayer
391- email = 'foo@example.com'
392-
393- def _create_inactive_person(self):
394- self.person = self.factory.makePerson(
395- email=self.email, email_address_status=EmailAddressStatus.NEW)
396- self.account = self.person.account
397- self.assertEquals(self.account.status, AccountStatus.NOACCOUNT)
398-
399- def test_inactive_accounts_are_activated(self):
400- # Resetting the password of an account in the NOACCOUNT state will
401- # activate it.
402- self._create_inactive_person()
403- self._resetPassword(ensure_no_errors=True)
404- self.assertEquals(self.account.status, AccountStatus.ACTIVE)
405- self.assertEquals(
406- self.account.preferredemail.email, 'foo@example.com')
407-
408- def _create_deactivated_person(self):
409- self.person = self.factory.makePerson(email=self.email)
410- removeSecurityProxy(self.person).deactivateAccount('Testing')
411- # Get the account from the master DB to make sure it has the changes
412- # we did above.
413- self.account = IMasterObject(self.person.account)
414- self.assertEquals(self.account.status, AccountStatus.DEACTIVATED)
415-
416- def test_deactivated_accounts_are_reactivated(self):
417- # Resetting the password of an account in the DEACTIVATED state will
418- # reactivate it.
419- self._create_deactivated_person()
420- self._resetPassword(ensure_no_errors=True)
421- self.assertEquals(self.account.status, AccountStatus.ACTIVE)
422- self.assertEquals(
423- self.account.preferredemail.email, 'foo@example.com')
424-
425- def _create_suspended_person(self):
426- self.person = self.factory.makePerson(email=self.email)
427- # Get the account from the master DB as we're going to change it.
428- self.account = IMasterObject(self.person.account)
429- removeSecurityProxy(self.account).status = AccountStatus.SUSPENDED
430- self.assertEquals(self.account.status, AccountStatus.SUSPENDED)
431-
432- def test_suspended_accounts_cannot_reset_password(self):
433- # It's not possible to reset the password of a SUSPENDED account.
434- self._create_suspended_person()
435- harness = self._resetPassword()
436- notifications = [notification.message
437- for notification in harness.request.notifications]
438- self.assertIn(
439- 'Your password cannot be reset because your account is suspended',
440- '\n'.join(notifications))
441- self.assertEquals(self.account.status, AccountStatus.SUSPENDED)
442-
443- def _resetPassword(self, ensure_no_errors=False):
444- token = getUtility(ILoginTokenSet).new(
445- self.person, self.email, self.email,
446- LoginTokenType.PASSWORDRECOVERY)
447- harness = LaunchpadFormHarness(token, ResetPasswordView)
448- form = {'field.email': self.email,
449- 'field.password': 'test',
450- 'field.password_dupe': 'test'}
451- harness.submit('continue', form)
452- if ensure_no_errors:
453- self.assertFalse(harness.hasErrors())
454- # Need to manually commit because we're interested in testing the
455- # changes done by the view on the token's requester (i.e.
456- # self.person).
457- transaction.commit()
458- return harness
459-
460-
461-def test_suite():
462- return unittest.TestLoader().loadTestsFromName(__name__)
463
464=== modified file 'lib/canonical/launchpad/database/logintoken.py'
465--- lib/canonical/launchpad/database/logintoken.py 2010-03-05 13:04:06 +0000
466+++ lib/canonical/launchpad/database/logintoken.py 2010-04-05 17:13:29 +0000
467@@ -152,23 +152,6 @@
468 subject = 'Launchpad: Confirm your OpenPGP Key'
469 self._send_email(from_name, subject, text)
470
471- def sendPasswordResetEmail(self):
472- """See ILoginToken."""
473- template = get_email_template('forgottenpassword.txt')
474- from_name = "Launchpad"
475- message = template % dict(token_url=canonical_url(self))
476- subject = "Launchpad: Forgotten Password"
477- self._send_email(from_name, subject, message)
478-
479- def sendNewUserEmail(self):
480- """See ILoginToken."""
481- template = get_email_template('newuser-email.txt')
482- message = template % dict(token_url=canonical_url(self))
483-
484- from_name = "Launchpad"
485- subject = "Launchpad: complete your registration"
486- self._send_email(from_name, subject, message)
487-
488 def sendProfileCreatedEmail(self, profile, comment):
489 """See ILoginToken."""
490 template = get_email_template('profile-created.txt')
491
492=== modified file 'lib/canonical/launchpad/doc/logintoken-pages.txt'
493--- lib/canonical/launchpad/doc/logintoken-pages.txt 2009-04-17 10:32:16 +0000
494+++ lib/canonical/launchpad/doc/logintoken-pages.txt 2010-04-05 17:13:29 +0000
495@@ -1,19 +1,18 @@
496 = LoginToken pages =
497
498-Users interact with login tokens for operations that require the user to
499-prove he has access to a resource that is external to Launchpad. For
500-example, claiming an email address or resetting a password require the
501-user to use the login token sent to him in an email.
502-
503-
504-== Reset password view ==
505-
506-A user can reset his password if he forgets it. A link for a LoginToken
507-is sent to the email address that the user claims is his. The view
508-displays a form asking the user to confirm his email address and provide
509-a new password.
510-
511- >>> from zope.component import getMultiAdapter, getUtility
512+Users interact with login tokens for operations that require the user to prove
513+he has access to a resource that is external to Launchpad. For example,
514+claiming an email address or a OpenPGP key require the user to use the login
515+token sent to him in an email.
516+
517+
518+== Claiming a profile ==
519+
520+The ClaimProfileView allows users to claim accounts created by Launchpad
521+processes. The user profile for Matsubara was created during an import.
522+The user profile does not have a preferred email address or a password.
523+
524+ >>> from zope.component import getMultiAdapter
525 >>> from canonical.launchpad.interfaces.authtoken import LoginTokenType
526 >>> from canonical.launchpad.interfaces.logintoken import (
527 ... ILoginTokenSet)
528@@ -24,159 +23,7 @@
529 # of gettint it as a zope secured utility. If we don't do that we'd have
530 # to remove the security proxy of the person objects all the time.
531 >>> person_set = PersonSet()
532- >>> no_priv = person_set.getByEmail('no-priv@canonical.com')
533- >>> old_password = no_priv.account.password
534-
535 >>> login_token_set = getUtility(ILoginTokenSet)
536- >>> login_token = login_token_set.new(
537- ... no_priv, 'no-priv@canonical.com',
538- ... 'no-priv@canonical.com', LoginTokenType.PASSWORDRECOVERY)
539- >>> request = LaunchpadTestRequest(
540- ... SERVER_URL='http://launchpad.dev',
541- ... PATH_INFO='/token/%s/+resetpassword' % login_token.token,
542- ... method='POST',
543- ... form={
544- ... 'field.email': 'no-priv@canonical.com',
545- ... 'field.password': 'test1',
546- ... 'field.password_dupe': 'test1',
547- ... 'field.actions.continue': 'Continue',
548- ... })
549- >>> login(ANONYMOUS, request)
550- >>> resetpassword_view = getMultiAdapter(
551- ... (login_token, request), name="+resetpassword")
552- >>> resetpassword_view.initialize()
553- >>> old_password == no_priv.account.password
554- False
555-
556-If the user submits the wrong email address for the token, an error is
557-stored. The password is not reset.
558-
559- >>> login_token = login_token_set.new(
560- ... no_priv, 'no-priv@canonical.com',
561- ... 'no-priv@canonical.com', LoginTokenType.PASSWORDRECOVERY)
562- >>> request = LaunchpadTestRequest(
563- ... SERVER_URL='http://launchpad.dev',
564- ... PATH_INFO='/token/%s/+resetpassword' % login_token.token,
565- ... method='POST',
566- ... form={
567- ... 'field.email': 'wrong@canonical.com',
568- ... 'field.password': 'test2',
569- ... 'field.password_dupe': 'test2',
570- ... 'field.actions.continue': 'Continue',
571- ... })
572- >>> login(ANONYMOUS, request)
573- >>> resetpassword_view = getMultiAdapter(
574- ... (login_token, request), name="+resetpassword")
575- >>> resetpassword_view.initialize()
576- >>> resetpassword_view.errors
577- [u"The email address ... didn't match ... the password reset."]
578-
579- >>> old_password == no_priv.account.password
580- True
581-
582-
583-=== Reactivating an account using reset password ===
584-
585-A user with a DEACTIVATED account does not have a password--he must use
586-the +resetpassword view to set his account to ACTIVE and add a password.
587-
588- >>> from canonical.launchpad.interfaces import IMasterObject
589- >>> former_user = IMasterObject(
590- ... person_set.getByEmail('former-user@canonical.com'))
591- >>> former_user.name
592- u'former-user-deactivatedaccount'
593-
594- >>> former_user.account.status
595- <DBItem AccountStatus.DEACTIVATED, ...>
596- >>> print former_user.account.password
597- None
598-
599- >>> login_token = login_token_set.new(
600- ... former_user, 'former-user@canonical.com',
601- ... 'former-user@canonical.com', LoginTokenType.PASSWORDRECOVERY)
602- >>> transaction.commit()
603-
604-If the user provides the correct email address for the token, the
605-password is reset, and the account status is set ACTIVE.
606-
607- >>> request = LaunchpadTestRequest(
608- ... SERVER_URL='http://launchpad.dev',
609- ... PATH_INFO='/token/%s/+resetpassword' % login_token.token,
610- ... method='POST',
611- ... form={
612- ... 'field.email': 'former-user@canonical.com',
613- ... 'field.password': 'test3',
614- ... 'field.password_dupe': 'test3',
615- ... 'field.actions.continue': 'Continue',
616- ... })
617- >>> login(ANONYMOUS, request)
618- >>> resetpassword_view = getMultiAdapter(
619- ... (login_token, request), name="+resetpassword")
620- >>> resetpassword_view.initialize()
621- >>> transaction.commit()
622-
623- >>> former_user.name
624- u'former-user'
625- >>> former_user.account.status
626- <DBItem AccountStatus.ACTIVE, ...>
627- >>> former_user.is_valid_person
628- True
629-
630-
631-== Reset password cannot reactivate a Suspended account ==
632-
633-A user with a SUSPENDED account cannot use +resetpassword view to set his
634-account to ACTIVE, nor will it reset his password.
635-
636- >>> from canonical.launchpad.interfaces.account import AccountStatus
637-
638- # Admins can suspend an account and access the user's password.
639- >>> login('foo.bar@canonical.com')
640- >>> suspended_user = factory.makePerson(
641- ... email='suspended-user@canonical.com',
642- ... name='suspended-user',
643- ... password='invalid')
644- >>> suspended_account = IMasterObject(suspended_user.account)
645- >>> suspended_account.status = AccountStatus.SUSPENDED
646- >>> original_password = suspended_account.password
647- >>> transaction.commit()
648- >>> suspended_user.is_valid_person
649- False
650- >>> suspended_account.status
651- <DBItem AccountStatus.SUSPENDED, ...>
652-
653- >>> login_token = login_token_set.new(
654- ... suspended_user, 'suspended-user@canonical.com',
655- ... 'suspended-user@canonical.com', LoginTokenType.PASSWORDRECOVERY)
656- >>> request = LaunchpadTestRequest(
657- ... SERVER_URL='http://launchpad.dev',
658- ... PATH_INFO='/token/%s/+resetpassword' % login_token.token,
659- ... method='POST',
660- ... form={
661- ... 'field.email': 'suspended-user@canonical.com',
662- ... 'field.password': 'bad-intentions',
663- ... 'field.password_dupe': 'bad-intentions',
664- ... 'field.actions.continue': 'Continue',
665- ... })
666- >>> login(ANONYMOUS, request)
667- >>> resetpassword_view = getMultiAdapter(
668- ... (login_token, request), name="+resetpassword")
669- >>> resetpassword_view.initialize()
670-
671- >>> login('foo.bar@canonical.com')
672- >>> suspended_user.is_valid_person
673- False
674- >>> suspended_user.account.password == original_password
675- True
676- >>> suspended_user.account.status
677- <DBItem AccountStatus.SUSPENDED, ...>
678-
679-
680-== Claiming a profile ==
681-
682-The ClaimProfileView allows users to claim accounts created by Launchpad
683-processes. The user profile for Matsubara was created during an import.
684-The user profile does not have a preferred email address or a password.
685
686 >>> matsubara = person_set.getByEmail('matsubara@async.com.br')
687 >>> matsubara.name
688@@ -240,135 +87,6 @@
689 True
690
691
692-== Creating a new ordinary account ==
693-
694-Unlike accounts created using OpenID, which don't have an associated Person
695-(as shown in sso-workflow-register.txt), ordinary accounts will always have
696-a Person entry associated with them.
697-
698- >>> login_token = login_token_set.new(
699- ... None, 'foo@debian.org', 'foo@debian.org',
700- ... LoginTokenType.NEWACCOUNT)
701- >>> request = LaunchpadTestRequest(
702- ... SERVER_URL='http://launchpad.dev',
703- ... PATH_INFO='/token/%s/+newaccount' % login_token.token,
704- ... method='POST',
705- ... form={
706- ... 'field.displayname': 'Andre Lopes',
707- ... 'field.hide_email_addresses.used': '',
708- ... 'field.password': 'test',
709- ... 'field.password_dupe': 'test',
710- ... 'field.actions.continue': 'Continue',
711- ... })
712- >>> login(ANONYMOUS, request)
713- >>> newaccount_view = getMultiAdapter(
714- ... (login_token, request), name="+newaccount")
715- >>> newaccount_view.initialize()
716- >>> newaccount_view.errors
717- []
718- >>> person_set.getByEmail('foo@debian.org')
719- <Person...
720-
721-
722-== Creating a new account from an existing profile ==
723-
724-If a user's email address matches the email address of an unclaimed
725-account, the existing profile is activated and given to the user.
726-Andre Lopes (andrelop) has an account created via an automated process.
727-
728- >>> andrelop = person_set.getByEmail('andrelop@debian.org')
729- >>> andrelop.name
730- u'andrelop'
731- >>> print andrelop.preferredemail
732- None
733-
734- >>> andrelop.account.status
735- <DBItem AccountStatus.NOACCOUNT, ...>
736- >>> andrelop.is_valid_person
737- False
738-
739-Andre would normally create a new account by claiming a LoginToken
740-that verifies his access to the email address his provided.
741-
742- >>> login_token = login_token_set.new(
743- ... andrelop, 'andrelop@debian.org',
744- ... 'andrelop@debian.org', LoginTokenType.NEWACCOUNT)
745- >>> transaction.commit()
746-
747-The NewAccountView will activate the existing account and update
748-the Person object.
749-
750- >>> request = LaunchpadTestRequest(
751- ... SERVER_URL='http://launchpad.dev',
752- ... PATH_INFO='/token/%s/+newaccount' % login_token.token,
753- ... method='POST',
754- ... form={
755- ... 'field.displayname': 'Andre Lopes',
756- ... 'field.hide_email_addresses.used': '',
757- ... 'field.password': 'test5',
758- ... 'field.password_dupe': 'test5',
759- ... 'field.actions.continue': 'Continue',
760- ... })
761- >>> login(ANONYMOUS, request)
762- >>> claimprofile_view = getMultiAdapter(
763- ... (login_token, request), name="+newaccount")
764- >>> claimprofile_view.initialize()
765- >>> claimprofile_view.errors
766- []
767- >>> transaction.commit()
768-
769-The existing Account, Person and EmailAddress are updated.
770-
771- >>> andrelop.displayname
772- u'Andre Lopes'
773- >>> andrelop.hide_email_addresses
774- False
775- >>> andrelop.account.preferredemail.email
776- u'andrelop@debian.org'
777-
778- >>> andrelop.account.status
779- <DBItem AccountStatus.ACTIVE, ...>
780- >>> andrelop.account.status_comment
781- u'Activated by new account.'
782- >>> andrelop.is_valid_person
783- True
784-
785-
786-== Creating a new account cannot reactivate a suspended profile ==
787-
788-An existing account that is SUSPENDED cannot be reactivated by claiming
789-the email address during account creation.
790-
791- >>> login_token = login_token_set.new(
792- ... suspended_user, 'suspended-user@canonical.com',
793- ... 'suspended-user@canonical.com', LoginTokenType.NEWACCOUNT)
794- >>> request = LaunchpadTestRequest(
795- ... SERVER_URL='http://launchpad.dev',
796- ... PATH_INFO='/token/%s/+newaccount' % login_token.token,
797- ... method='POST',
798- ... form={
799- ... 'field.displayname': 'Bad User',
800- ... 'field.hide_email_addresses.used': '',
801- ... 'field.password': 'bad-intentions',
802- ... 'field.password_dupe': 'bad-intentions',
803- ... 'field.actions.continue': 'Continue',
804- ... })
805- >>> login(ANONYMOUS, request)
806- >>> claimprofile_view = getMultiAdapter(
807- ... (login_token, request), name="+newaccount")
808- >>> claimprofile_view.initialize()
809- >>> claimprofile_view.errors
810- []
811-
812- >>> login('foo.bar@canonical.com')
813- >>> suspended_user.is_valid_person
814- False
815- >>> suspended_user.account.password == original_password
816- True
817- >>> suspended_user.account.status
818- <DBItem AccountStatus.SUSPENDED, ...>
819-
820-
821 == Validating GPG keys ==
822
823 In order to add a GPG key to Launchpad we require that the person
824
825=== removed file 'lib/canonical/launchpad/emailtemplates/forgottenpassword-neutral.txt'
826--- lib/canonical/launchpad/emailtemplates/forgottenpassword-neutral.txt 2007-06-19 15:54:26 +0000
827+++ lib/canonical/launchpad/emailtemplates/forgottenpassword-neutral.txt 1970-01-01 00:00:00 +0000
828@@ -1,14 +0,0 @@
829-Hello
830-
831-You have requested a new password for your Launchpad Login Service account.
832-
833-To change your password:
834-
835- %(token_url)s
836-
837-If you don't know what this is about, then someone else has entered
838-your e-mail address at the Launchpad Login Service. Sorry about
839-that. You don't need to do anything further, just delete this message.
840-
841-Regards,
842-The Launchpad Login Service team
843
844=== removed file 'lib/canonical/launchpad/emailtemplates/forgottenpassword.txt'
845--- lib/canonical/launchpad/emailtemplates/forgottenpassword.txt 2006-08-31 17:52:22 +0000
846+++ lib/canonical/launchpad/emailtemplates/forgottenpassword.txt 1970-01-01 00:00:00 +0000
847@@ -1,11 +0,0 @@
848-Hi
849-
850-You, or someone posing as you, has requested a new password for
851-your Launchpad account.
852-
853-To reset your password, please click on the link below.
854-You will need to enter this email address, enter and confirm your new password.
855-
856- %(token_url)s
857-
858-The Launchpad Team
859
860=== removed file 'lib/canonical/launchpad/emailtemplates/newuser-email-neutral.txt'
861--- lib/canonical/launchpad/emailtemplates/newuser-email-neutral.txt 2007-06-19 15:54:26 +0000
862+++ lib/canonical/launchpad/emailtemplates/newuser-email-neutral.txt 1970-01-01 00:00:00 +0000
863@@ -1,15 +0,0 @@
864-Hello
865-
866-Thank you for registering with the Launchpad Login Service.
867-
868-To complete your registration:
869-
870- %(token_url)s
871-
872-If you don't know what this is about, then someone has probably
873-entered your e-mail address by mistake at the Launchpad Login Service
874-Web site. Sorry about that. You don't need to do anything further,
875-just delete this message.
876-
877-Regards,
878-The Launchpad Login Service team
879
880=== removed file 'lib/canonical/launchpad/emailtemplates/newuser-email.txt'
881--- lib/canonical/launchpad/emailtemplates/newuser-email.txt 2008-11-17 17:17:33 +0000
882+++ lib/canonical/launchpad/emailtemplates/newuser-email.txt 1970-01-01 00:00:00 +0000
883@@ -1,19 +0,0 @@
884-Hello!
885-
886-Thank you for registering at Launchpad. To complete your
887-registration, please follow the instructions here:
888-
889- %(token_url)s
890-
891-Launchpad is a suite of applications for people who work with
892-free software projects. Using Launchpad you can report bugs, help
893-with translation, find the answer to a question, find and share
894-code and host your own free software project.
895-
896-Once you've completed the registration, take a look at our
897-"getting started" guide here:
898-
899- https://help.launchpad.net/NewToLaunchpad
900-
901-Thanks,
902-The Launchpad team.
903
904=== modified file 'lib/canonical/launchpad/interfaces/authtoken.py'
905--- lib/canonical/launchpad/interfaces/authtoken.py 2009-10-13 16:59:43 +0000
906+++ lib/canonical/launchpad/interfaces/authtoken.py 2010-04-05 17:13:29 +0000
907@@ -10,7 +10,6 @@
908 __all__ = [
909 'LoginTokenType',
910 'IAuthToken',
911- 'IAuthTokenSet',
912 ]
913
914 from zope.schema import Choice, Datetime, Int, Text, TextLine
915@@ -118,6 +117,9 @@
916 """)
917
918
919+# XXX: Guilherme Salgado, 2010-03-30: This interface was created to be used by
920+# our old OpenID provider, but that doesn't exist anymore, so we should merge
921+# it with ILoginToken.
922 class IAuthToken(Interface):
923 """The object that stores one time tokens used for validating email
924 addresses and other tasks that require verifying if an email address is
925@@ -193,55 +195,3 @@
926 """Send an email message to the requester with a magic URL that allows
927 him to finish the Launchpad registration process.
928 """
929-
930-
931-class IAuthTokenSet(Interface):
932- """The set of AuthTokens."""
933-
934- title = Attribute('Title')
935-
936- def get(id, default=None):
937- """Return the AuthToken object with the given id.
938-
939- Return the default value if there's no such AuthToken.
940- """
941-
942- def searchByEmailAccountAndType(email, account, type, consumed=None):
943- """Return all AuthTokens for the given email, account and type.
944-
945- :param email: The email address to search for.
946- :param account: The Account object representing the requester
947- to search for.
948- :param type: The AuthTokenType to search for.
949- :param consumed: A flag indicating whether to return consumed tokens.
950- If False, only unconsumed tokens will be returned.
951- If True, only consumed tokens will be returned.
952- If None, this parameter will be ignored and all tokens will be
953- returned.
954- """
955-
956- def deleteByEmailAccountAndType(email, account, type):
957- """Delete all AuthToken entries with the given email,
958- requester account and type."""
959-
960- def new(requester, requesteremail, email, tokentype, redirection_url):
961- """Create a new AuthToken object.
962-
963- :param requester: a Person object or None (in case of a new
964- account)
965- :param requesteremail: the email address used to login on the
966- system. Can also be None in case of a new account
967- :param email: the email address that this request will be sent
968- to. It should be previously validated by valid_email()
969- :param tokentype: the type of the request, according to
970- LoginTokenType.
971- :param redirection_url: the URL the user will be forwarded to
972- after consuming the token. May be None.
973- """
974-
975- def __getitem__(id):
976- """Returns the AuthToken with the given id.
977-
978- Raises KeyError if there is no such AuthToken.
979- """
980-
981
982=== removed file 'lib/canonical/launchpad/templates/logintoken-newaccount.pt'
983--- lib/canonical/launchpad/templates/logintoken-newaccount.pt 2009-09-16 20:51:57 +0000
984+++ lib/canonical/launchpad/templates/logintoken-newaccount.pt 1970-01-01 00:00:00 +0000
985@@ -1,19 +0,0 @@
986-<html
987- xmlns="http://www.w3.org/1999/xhtml"
988- xmlns:tal="http://xml.zope.org/namespaces/tal"
989- xmlns:metal="http://xml.zope.org/namespaces/metal"
990- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
991- metal:use-macro="view/macro:page/locationless"
992- i18n:domain="launchpad"
993->
994- <body>
995-
996- <div metal:fill-slot="main">
997-
998- <div metal:use-macro="context/@@launchpad_form/form" />
999-
1000- </div>
1001- </body>
1002-
1003-</html>
1004-
1005
1006=== removed file 'lib/canonical/launchpad/templates/logintoken-resetpassword.pt'
1007--- lib/canonical/launchpad/templates/logintoken-resetpassword.pt 2009-09-16 20:51:57 +0000
1008+++ lib/canonical/launchpad/templates/logintoken-resetpassword.pt 1970-01-01 00:00:00 +0000
1009@@ -1,18 +0,0 @@
1010-<html
1011- xmlns="http://www.w3.org/1999/xhtml"
1012- xmlns:tal="http://xml.zope.org/namespaces/tal"
1013- xmlns:metal="http://xml.zope.org/namespaces/metal"
1014- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
1015- metal:use-macro="view/macro:page/locationless"
1016- i18n:domain="launchpad"
1017->
1018- <body>
1019-
1020-<div metal:fill-slot="main">
1021-
1022- <div metal:use-macro="context/@@launchpad_form/form" />
1023-
1024-</div>
1025-
1026-</body>
1027-</html>
1028
1029=== modified file 'lib/canonical/launchpad/tests/test_login.py'
1030--- lib/canonical/launchpad/tests/test_login.py 2009-07-17 00:26:05 +0000
1031+++ lib/canonical/launchpad/tests/test_login.py 2010-04-05 17:13:29 +0000
1032@@ -13,13 +13,11 @@
1033 from canonical.launchpad.ftests import ANONYMOUS, login
1034 from canonical.launchpad.interfaces.account import (
1035 AccountCreationRationale, IAccountSet)
1036-from lp.registry.interfaces.person import IPerson
1037 from lp.testing import TestCaseWithFactory
1038 from canonical.launchpad.webapp.authentication import LaunchpadPrincipal
1039 from canonical.launchpad.webapp.interfaces import (
1040 CookieAuthLoggedInEvent, ILaunchpadPrincipal, IPlacelessAuthUtility)
1041-from canonical.launchpad.webapp.login import (
1042- logInPrincipal, logInPrincipalAndMaybeCreatePerson, logoutPerson)
1043+from canonical.launchpad.webapp.login import logInPrincipal, logoutPerson
1044 from canonical.launchpad.webapp.servers import LaunchpadTestRequest
1045 from canonical.testing import DatabaseFunctionalLayer
1046
1047@@ -137,23 +135,6 @@
1048 self.failUnless(ILaunchpadPrincipal.providedBy(principal))
1049 self.failUnless(principal.person is None)
1050
1051- def test_logInPrincipalAndMaybeCreatePerson(self):
1052- # logInPrincipalAndMaybeCreatePerson() will log the given principal in
1053- # and create a Person entry associated with it if one doesn't exist
1054- # already.
1055- logInPrincipalAndMaybeCreatePerson(
1056- self.request, self.principal, 'foo@example.com')
1057-
1058- # This is so that the authenticate() call below uses cookie auth.
1059- self.request.response.setCookie(
1060- config.launchpad_session.cookie, 'xxx')
1061-
1062- principal = getUtility(IPlacelessAuthUtility).authenticate(
1063- self.request)
1064- self.failUnless(ILaunchpadPrincipal.providedBy(principal))
1065- person = IPerson(principal.account)
1066- self.failUnless(IPerson.providedBy(person))
1067-
1068
1069 def test_suite():
1070 return unittest.TestLoader().loadTestsFromName(__name__)
1071
1072=== modified file 'lib/canonical/launchpad/tests/test_token_creation.py'
1073--- lib/canonical/launchpad/tests/test_token_creation.py 2009-06-25 05:30:52 +0000
1074+++ lib/canonical/launchpad/tests/test_token_creation.py 2010-04-05 17:13:29 +0000
1075@@ -36,9 +36,9 @@
1076
1077 # Now insert the token in the table so that the next time we call
1078 # create_unique_token_for_table() we get a different token.
1079- login_token = LoginToken(
1080+ LoginToken(
1081 requester=None, token=token2, email='email@example.com',
1082- tokentype=LoginTokenType.NEWACCOUNT, created=UTC_NOW)
1083+ tokentype=LoginTokenType.ACCOUNTMERGE, created=UTC_NOW)
1084 random.seed(0)
1085 token3 = create_unique_token_for_table(99, LoginToken.token)
1086 self.assertNotEquals(token1, token3)
1087
1088=== modified file 'lib/canonical/launchpad/webapp/login.py'
1089--- lib/canonical/launchpad/webapp/login.py 2010-03-30 14:57:02 +0000
1090+++ lib/canonical/launchpad/webapp/login.py 2010-04-05 17:13:29 +0000
1091@@ -38,8 +38,8 @@
1092 from canonical.launchpad.webapp.dbpolicy import MasterDatabasePolicy
1093 from canonical.launchpad.webapp.error import SystemErrorView
1094 from canonical.launchpad.webapp.interfaces import (
1095- CookieAuthLoggedInEvent, ILaunchpadApplication, ILaunchpadPrincipal,
1096- IPlacelessAuthUtility, IPlacelessLoginSource, LoggedOutEvent)
1097+ CookieAuthLoggedInEvent, ILaunchpadApplication, IPlacelessAuthUtility,
1098+ IPlacelessLoginSource, LoggedOutEvent)
1099 from canonical.launchpad.webapp.metazcml import ILaunchpadPermission
1100 from canonical.launchpad.webapp.publisher import LaunchpadView
1101 from canonical.launchpad.webapp.url import urlappend
1102@@ -378,24 +378,6 @@
1103 notify(CookieAuthLoggedInEvent(request, email))
1104
1105
1106-def logInPrincipalAndMaybeCreatePerson(request, principal, email):
1107- """Log the principal in, creating a Person if necessary.
1108-
1109- If the given principal has no associated person, we create a new
1110- person, fetch a new principal and set it in the request.
1111-
1112- Password validation must be done in callsites.
1113- """
1114- logInPrincipal(request, principal, email)
1115- if ILaunchpadPrincipal.providedBy(principal) and principal.person is None:
1116- person = principal.account.createPerson(
1117- PersonCreationRationale.OWNER_CREATED_LAUNCHPAD)
1118- new_principal = getUtility(IPlacelessLoginSource).getPrincipal(
1119- principal.id)
1120- assert ILaunchpadPrincipal.providedBy(new_principal)
1121- request.setPrincipal(new_principal)
1122-
1123-
1124 def expireSessionCookie(request, client_id_manager=None,
1125 delta=timedelta(minutes=10)):
1126 if client_id_manager is None:
1127
1128=== modified file 'lib/canonical/launchpad/webapp/publication.py'
1129--- lib/canonical/launchpad/webapp/publication.py 2010-03-30 14:33:26 +0000
1130+++ lib/canonical/launchpad/webapp/publication.py 2010-04-05 17:13:29 +0000
1131@@ -317,7 +317,7 @@
1132 if request.method != 'POST':
1133 return
1134 # XXX: jamesh 2007-11-23 bug=124421:
1135- # Allow offsite posts to our OpenID endpoint. Ideally we'd
1136+ # Allow offsite posts to our TestOpenID endpoint. Ideally we'd
1137 # have a better way of marking this URL as allowing offsite
1138 # form posts.
1139 if request['PATH_INFO'] == '/+openid':
1140
1141=== modified file 'lib/canonical/launchpad/zcml/logintoken.zcml'
1142--- lib/canonical/launchpad/zcml/logintoken.zcml 2009-07-18 00:05:49 +0000
1143+++ lib/canonical/launchpad/zcml/logintoken.zcml 2010-04-05 17:13:29 +0000
1144@@ -36,22 +36,6 @@
1145 />
1146
1147 <browser:page
1148- name="+newaccount"
1149- for="canonical.launchpad.interfaces.ILoginToken"
1150- class="canonical.launchpad.browser.logintoken.NewUserAccountView"
1151- permission="zope.Public"
1152- template="../../launchpad/templates/logintoken-newaccount.pt"
1153- />
1154-
1155- <browser:page
1156- for="canonical.launchpad.interfaces.ILoginToken"
1157- name="+resetpassword"
1158- permission="zope.Public"
1159- class="canonical.launchpad.browser.logintoken.ResetPasswordView"
1160- template="../templates/logintoken-resetpassword.pt"
1161- />
1162-
1163- <browser:page
1164 for="canonical.launchpad.interfaces.ILoginToken"
1165 name="+validateemail"
1166 permission="zope.Public"
1167
1168=== modified file 'lib/lp/registry/browser/configure.zcml'
1169--- lib/lp/registry/browser/configure.zcml 2010-03-24 22:06:30 +0000
1170+++ lib/lp/registry/browser/configure.zcml 2010-04-05 17:13:29 +0000
1171@@ -1192,12 +1192,6 @@
1172 classes="
1173 PersonSetNavigation"/>
1174 <browser:page
1175- name="+newperson"
1176- for="lp.registry.interfaces.person.IPersonSet"
1177- class="lp.registry.browser.person.PersonAddView"
1178- permission="launchpad.Admin"
1179- template="../templates/people-newperson.pt"/>
1180- <browser:page
1181 name="+newteam"
1182 for="lp.registry.interfaces.person.IPersonSet"
1183 class="lp.registry.browser.team.TeamAddView"
1184
1185=== modified file 'lib/lp/registry/browser/person.py'
1186--- lib/lp/registry/browser/person.py 2010-03-25 14:50:31 +0000
1187+++ lib/lp/registry/browser/person.py 2010-04-05 17:13:29 +0000
1188@@ -10,10 +10,8 @@
1189 'BeginTeamClaimView',
1190 'BugSubscriberPackageBugsSearchListingView',
1191 'EmailToPersonView',
1192- 'PeopleSearchMenu'
1193 'PeopleSearchView',
1194 'PersonAccountAdministerView',
1195- 'PersonAddView',
1196 'PersonAdministerView',
1197 'PersonAnswerContactForView',
1198 'PersonAnswersMenu',
1199@@ -163,9 +161,8 @@
1200 from lp.registry.interfaces.mailinglistsubscription import (
1201 MailingListAutoSubscribePolicy)
1202 from lp.registry.interfaces.person import (
1203- INewPerson, IPerson, IPersonClaim, IPersonSet, ITeam, ITeamReassignment,
1204- PersonCreationRationale, PersonVisibility, TeamMembershipRenewalPolicy,
1205- TeamSubscriptionPolicy)
1206+ IPerson, IPersonClaim, IPersonSet, ITeam, ITeamReassignment,
1207+ PersonVisibility, TeamMembershipRenewalPolicy, TeamSubscriptionPolicy)
1208 from lp.registry.interfaces.poll import IPollSet, IPollSubset
1209 from lp.registry.interfaces.ssh import ISSHKeySet, SSHKeyType
1210 from lp.registry.interfaces.teammembership import (
1211@@ -1375,33 +1372,6 @@
1212 return BatchNavigator(results, self.request)
1213
1214
1215-class PersonAddView(LaunchpadFormView):
1216- """The page where users can create new Launchpad profiles."""
1217-
1218- page_title = "Create a new Launchpad profile"
1219- label = page_title
1220- schema = INewPerson
1221-
1222- custom_widget('creation_comment', TextAreaWidget, height=5, width=60)
1223-
1224- @action(_("Create Profile"), name="create")
1225- def create_action(self, action, data):
1226- emailaddress = data['emailaddress']
1227- displayname = data['displayname']
1228- creation_comment = data['creation_comment']
1229- person, ignored = getUtility(IPersonSet).createPersonAndEmail(
1230- emailaddress, PersonCreationRationale.USER_CREATED,
1231- displayname=displayname, comment=creation_comment,
1232- registrant=self.user)
1233- self.next_url = canonical_url(person)
1234- logintokenset = getUtility(ILoginTokenSet)
1235- token = logintokenset.new(
1236- requester=self.user,
1237- requesteremail=self.user.preferredemail.email,
1238- email=emailaddress, tokentype=LoginTokenType.NEWPROFILE)
1239- token.sendProfileCreatedEmail(person, creation_comment)
1240-
1241-
1242 class DeactivateAccountSchema(Interface):
1243 comment = copy_field(
1244 IPerson['account_status_comment'], readonly=False, __name__='comment')
1245
1246=== modified file 'lib/lp/registry/interfaces/person.py'
1247--- lib/lp/registry/interfaces/person.py 2010-03-25 14:17:56 +0000
1248+++ lib/lp/registry/interfaces/person.py 2010-04-05 17:13:29 +0000
1249@@ -11,11 +11,8 @@
1250 'IAdminPeopleMergeSchema',
1251 'IAdminTeamMergeSchema',
1252 'IHasStanding',
1253- 'INewPerson',
1254- 'INewPersonForm',
1255 'IObjectReassignment',
1256 'IPerson',
1257- 'IPersonChangePassword',
1258 'IPersonClaim',
1259 'IPersonPublic', # Required for a monkey patch in interfaces/archive.py
1260 'IPersonSet',
1261@@ -72,8 +69,7 @@
1262 from canonical.launchpad.interfaces.emailaddress import IEmailAddress
1263 from canonical.launchpad.interfaces.launchpad import (
1264 IHasIcon, IHasLogo, IHasMugshot, IPrivacy)
1265-from canonical.launchpad.interfaces.validation import (
1266- validate_new_person_email, validate_new_team_email)
1267+from canonical.launchpad.interfaces.validation import validate_new_team_email
1268 from canonical.launchpad.validators import LaunchpadValidationError
1269 from canonical.launchpad.validators.email import email_validator
1270 from canonical.launchpad.validators.name import name_validator
1271@@ -435,37 +431,12 @@
1272 super(PersonNameField, self)._validate(input)
1273
1274
1275-# XXX: salgado, 2010/03/05, bug=532688: This is currently used by c-i-p, so it
1276-# can't be removed yet. As soon as we stop using c-i-p, though, we'll be able
1277-# to remove this.
1278-class IPersonChangePassword(Interface):
1279- """The schema used by Person +changepassword form."""
1280-
1281- currentpassword = PasswordField(
1282- title=_('Current password'), required=True, readonly=False)
1283-
1284- password = PasswordField(
1285- title=_('New password'), required=True, readonly=False)
1286-
1287-
1288 class IPersonClaim(Interface):
1289 """The schema used by IPerson's +claim form."""
1290
1291 emailaddress = TextLine(title=_('Email address'), required=True)
1292
1293
1294-class INewPerson(Interface):
1295- """The schema used by IPersonSet's +newperson form."""
1296-
1297- emailaddress = StrippedTextLine(
1298- title=_('Email address'), required=True,
1299- constraint=validate_new_person_email)
1300- displayname = StrippedTextLine(title=_('Display name'), required=True)
1301- creation_comment = Text(
1302- title=_('Creation reason'), required=True,
1303- description=_("The reason why you're creating this profile."))
1304-
1305-
1306 # This has to be defined here to avoid circular import problems.
1307 class IHasStanding(Interface):
1308 """An object that can have personal standing."""
1309@@ -1544,16 +1515,6 @@
1310 PersonChoice.schema = IPerson
1311
1312
1313-class INewPersonForm(IPerson):
1314- """Interface used to create new Launchpad accounts.
1315-
1316- The only change with `IPerson` is a customised Password field.
1317- """
1318-
1319- password = PasswordField(
1320- title=_('Create password'), required=True, readonly=False)
1321-
1322-
1323 class ITeamPublic(Interface):
1324 """Public attributes of a Team."""
1325
1326
1327=== removed file 'lib/lp/registry/stories/person/xx-new-profile.txt'
1328--- lib/lp/registry/stories/person/xx-new-profile.txt 2009-11-22 15:43:16 +0000
1329+++ lib/lp/registry/stories/person/xx-new-profile.txt 1970-01-01 00:00:00 +0000
1330@@ -1,110 +0,0 @@
1331-
1332-We allow Launchpad admins to create random profiles that can latter be used
1333-throughout Launchpad. These profiles are not considered validated and because
1334-of that we won't send any email notification to them, except for an initial
1335-email explaining that a given user has created a Launchpad profile for that
1336-email address' owner.
1337-
1338- >>> admin_browser.open('http://launchpad.dev/people/+newperson')
1339- >>> admin_browser.url
1340- 'http://launchpad.dev/people/+newperson'
1341-
1342-All fields (email address, displayname and creation reason) in that form
1343-are required.
1344-
1345- >>> admin_browser.getControl('Create Profile').click()
1346- >>> print admin_browser.contents
1347- <...
1348- ...class="error message">There are 3 errors...
1349-
1350-Trying to create a profile with an invalid or already used email address
1351-is not allowed.
1352-
1353- >>> admin_browser.getControl('Email address').value = 'foo@'
1354- >>> admin_browser.getControl('Display name').value = 'Foo'
1355- >>> admin_browser.getControl('Creation reason').value = (
1356- ... 'I was just playing around and found this nice form, so I '
1357- ... 'decided to create a new profile to test.')
1358- >>> admin_browser.getControl('Create Profile').click()
1359- >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
1360- ... print tag.renderContents()
1361- There is 1 error.
1362- foo@ isn't a valid email address.
1363-
1364- >>> admin_browser.getControl('Email address').value = 'test@canonical.com'
1365- >>> admin_browser.getControl('Create Profile').click()
1366- >>> for tag in find_tags_by_class(admin_browser.contents, 'message'):
1367- ... print tag.renderContents()
1368- There is 1 error.
1369- The profile you're trying to create already exists:...
1370-
1371-When all fields are filled correctly, the new profile is created.
1372-
1373- >>> admin_browser.getControl('Email address').value = 'test2@canonical.com'
1374- >>> admin_browser.getControl('Create Profile').click()
1375- >>> admin_browser.url
1376- 'http://launchpad.dev/~test2'
1377-
1378- >>> print admin_browser.contents
1379- <...
1380- ...This page was created...
1381- ...by ...
1382- ...and the reason given by that user for its creation is:...
1383- ...I was just playing around and found this nice form,...
1384-
1385-The creation of this new profile will trigger an email notification to the
1386-profile's email address. This email will contain the name of the person who
1387-created the profile as well as the reason left by that person. It'll also
1388-contain a link to a page where it's possible to finish the registration
1389-process in order to validate that profile.
1390-
1391- >>> import email
1392- >>> from lp.services.mail import stub
1393- >>> from canonical.launchpad.ftests.logintoken import (
1394- ... get_token_url_from_email)
1395- >>> len(stub.test_emails)
1396- 1
1397- >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
1398- >>> token_url = get_token_url_from_email(raw_msg)
1399-
1400- >>> print email.message_from_string(raw_msg).get_payload()
1401- Hello!
1402- ... has created a new Launchpad profile [1] for
1403- you, and left the following comment as the reason for its creation:
1404- ...
1405- If you wish to use this profile, please follow the instructions here:
1406- ...
1407-
1408- >>> browser.open(token_url)
1409- >>> browser.url
1410- 'http://launchpad.dev/token/.../+newaccount'
1411-
1412-We don't prefill the new account's display name because the one we have was
1413-not entered by the users themselves.
1414-
1415- >>> browser.getControl('Display Name').value
1416- ''
1417-
1418-We also explain the users what to write in each form:
1419-
1420- >>> for tag in find_tags_by_class(browser.contents, 'formHelp'):
1421- ... print extract_text(tag)
1422- Your name as you would like it displayed throughout Launchpad...
1423-
1424-Now we fill the form and save it to create the new account.
1425-
1426- >>> browser.getControl('Display Name').value = 'My name'
1427- >>> browser.getControl('Create password').value = 'aaa'
1428- >>> browser.getControl(name='field.password_dupe').value = 'aaa'
1429- >>> browser.getControl('Continue').click()
1430-
1431- >>> browser.url
1432- 'http://launchpad.dev/~test2'
1433-
1434-
1435-Non-launchpad admins can't acces the page to ceate a new profile.
1436-
1437- >>> user_browser.open('http://launchpad.dev/people/+newperson')
1438- Traceback (most recent call last):
1439- ...
1440- Unauthorized: ...
1441
1442=== removed file 'lib/lp/registry/templates/people-newperson.pt'
1443--- lib/lp/registry/templates/people-newperson.pt 2009-08-31 17:46:33 +0000
1444+++ lib/lp/registry/templates/people-newperson.pt 1970-01-01 00:00:00 +0000
1445@@ -1,29 +0,0 @@
1446-<html
1447- xmlns="http://www.w3.org/1999/xhtml"
1448- xmlns:tal="http://xml.zope.org/namespaces/tal"
1449- xmlns:metal="http://xml.zope.org/namespaces/metal"
1450- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
1451- metal:use-macro="view/macro:page/main_only"
1452- i18n:domain="launchpad"
1453- >
1454- <body>
1455- <div metal:fill-slot="main">
1456- <div metal:use-macro="context/@@launchpad_form/form">
1457- <div metal:fill-slot="extra_info" class="application-summary">
1458- <p>
1459- Launchpad will send a message to the email address you provide
1460- below. This message explains that you created a Launchpad profile
1461- for the email address's owner, and it will include your name and
1462- email address so that the user can contact you if they want to.
1463- </p>
1464- <p>
1465- It will not be possible to log in with this new profile, and
1466- Launchpad will not send any other messages to it before its email
1467- address is confirmed. The registration process must be finished by
1468- the person represented by the new profile.
1469- </p>
1470- </div>
1471- </div>
1472- </div>
1473- </body>
1474-</html>