Merge lp:~bac/launchpad/bug-452491-captcha2-boogaloo into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Merged at revision: not available
Proposed branch: lp:~bac/launchpad/bug-452491-captcha2-boogaloo
Merge into: lp:launchpad
Diff against target: 451 lines
9 files modified
lib/canonical/launchpad/browser/tests/registration.py (+1/-1)
lib/canonical/launchpad/pagetests/standalone/xx-new-account-redirection-url.txt (+1/-1)
lib/canonical/launchpad/templates/launchpad-forgottenpassword.pt (+34/-7)
lib/canonical/launchpad/templates/launchpad-login.pt (+1/-0)
lib/canonical/launchpad/webapp/login.py (+48/-38)
lib/lp/registry/stories/foaf/xx-createaccount.txt (+2/-2)
lib/lp/registry/stories/foaf/xx-reg-with-existing-email.txt (+4/-4)
lib/lp/registry/stories/foaf/xx-resetpassword.txt (+34/-24)
lib/lp/testing/registration.py (+2/-2)
To merge this branch: bzr merge lp:~bac/launchpad/bug-452491-captcha2-boogaloo
Reviewer Review Type Date Requested Status
Martin Albisetti (community) ui Approve
Abel Deuring (community) Approve
Review via email: mp+13487@code.launchpad.net

Commit message

Add simple captcha to the +forgottenpassword page.

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

= Summary =

Bug 452491 requests a simple captcha be placed on the forgotten password page.

== Proposed fix ==

The captcha already existed on the registration page. It was quick work to refactor
the bits into a mixin class and let the forgotten password page use it. Sadly both
of those pages are done without using LaunchpadFormView, made instead with grout and
twine, so it took a little massaging to get things to work.

The test helper set_captcha_answer had to be made a little smarter to account for
form prefixes.

The reset password test needed some clean up too. There is a fair amount of drive by
stuff in here but it's easy to sort out, I hope.

I also changed the spacing on the error messages for this page and the registration
page to fix some overlap problems. Screenshots are at:
http://people.canonical.com/~bac/captcha/

== Pre-implementation notes ==

None

== Implementation details ==

As above.

== Tests ==

bin/test -vvm lp.registry -t foaf -t xx-new-account-redirection-url.txt

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/templates/launchpad-login.pt
  lib/canonical/launchpad/templates/launchpad-forgottenpassword.pt
  lib/lp/registry/stories/foaf/xx-reg-with-existing-email.txt
  lib/lp/registry/stories/foaf/xx-createaccount.txt
  lib/canonical/launchpad/browser/tests/registration.py
  lib/canonical/launchpad/webapp/login.py
  lib/lp/testing/registration.py
  lib/canonical/launchpad/pagetests/standalone/xx-new-account-redirection-url.txt
  lib/lp/registry/stories/foaf/xx-resetpassword.txt

Revision history for this message
Abel Deuring (adeuring) wrote :

Hi Brad,

a nice branch r=me. I have only a minor cosmetic suggestion, see below.

Abel

> === modified file 'lib/lp/testing/registration.py'
> --- lib/lp/testing/registration.py 2009-10-08 20:22:25 +0000
> +++ lib/lp/testing/registration.py 2009-10-16 16:35:20 +0000
> @@ -24,9 +24,10 @@
> return ''
>
>
> -def set_captcha_answer(browser, answer=None):
> +def set_captcha_answer(browser, answer=None, prefix=''):
> """Given a browser, set the login captcha with the correct answer."""
> if answer is None:
> answer = get_captcha_answer(browser.contents)
> - browser.getControl(name='loginpage_captcha_submission').value = (
> + control_name = prefix + 'captcha_submission'
> + browser.getControl(name=control_name).value = (
> answer)

I think you can merge the last two lines in one ;)

review: Approve
Revision history for this message
Brad Crittenden (bac) wrote :

> Hi Brad,
>
> a nice branch r=me. I have only a minor cosmetic suggestion, see below.
>
> Abel
>
> > === modified file 'lib/lp/testing/registration.py'
> > --- lib/lp/testing/registration.py 2009-10-08 20:22:25 +0000
> > +++ lib/lp/testing/registration.py 2009-10-16 16:35:20 +0000
> > @@ -24,9 +24,10 @@
> > return ''
> >
> >
> > -def set_captcha_answer(browser, answer=None):
> > +def set_captcha_answer(browser, answer=None, prefix=''):
> > """Given a browser, set the login captcha with the correct answer."""
> > if answer is None:
> > answer = get_captcha_answer(browser.contents)
> > - browser.getControl(name='loginpage_captcha_submission').value = (
> > + control_name = prefix + 'captcha_submission'
> > + browser.getControl(name=control_name).value = (
> > answer)
>
> I think you can merge the last two lines in one ;)

Done

Revision history for this message
Martin Albisetti (beuno) wrote :

Bonus points if the forms look like everywhere else in Launchpad.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/browser/tests/registration.py'
--- lib/canonical/launchpad/browser/tests/registration.py 2009-10-08 19:52:06 +0000
+++ lib/canonical/launchpad/browser/tests/registration.py 2009-10-16 19:59:17 +0000
@@ -21,7 +21,7 @@
21 browser = setupBrowser()21 browser = setupBrowser()
22 browser.open('http://launchpad.dev/+login')22 browser.open('http://launchpad.dev/+login')
23 browser.getControl(name='loginpage_email', index=1).value = email23 browser.getControl(name='loginpage_email', index=1).value = email
24 set_captcha_answer(browser)24 set_captcha_answer(browser, prefix='loginpage_')
25 browser.getControl('Register').click()25 browser.getControl('Register').click()
26 return browser26 return browser
2727
2828
=== modified file 'lib/canonical/launchpad/pagetests/standalone/xx-new-account-redirection-url.txt'
--- lib/canonical/launchpad/pagetests/standalone/xx-new-account-redirection-url.txt 2009-10-08 19:52:06 +0000
+++ lib/canonical/launchpad/pagetests/standalone/xx-new-account-redirection-url.txt 2009-10-16 19:59:17 +0000
@@ -42,7 +42,7 @@
42 >>> from lp.testing.registration import set_captcha_answer42 >>> from lp.testing.registration import set_captcha_answer
43 >>> anon_browser.getControl('E-mail address:', index=1).value = (43 >>> anon_browser.getControl('E-mail address:', index=1).value = (
44 ... 'granny@canonical.com')44 ... 'granny@canonical.com')
45 >>> set_captcha_answer(anon_browser)45 >>> set_captcha_answer(anon_browser, prefix='loginpage_')
46 >>> anon_browser.getControl('Register').click()46 >>> anon_browser.getControl('Register').click()
47 >>> print anon_browser.contents47 >>> print anon_browser.contents
48 <...48 <...
4949
=== modified file 'lib/canonical/launchpad/templates/launchpad-forgottenpassword.pt'
--- lib/canonical/launchpad/templates/launchpad-forgottenpassword.pt 2009-07-17 17:59:07 +0000
+++ lib/canonical/launchpad/templates/launchpad-forgottenpassword.pt 2009-10-16 19:59:17 +0000
@@ -21,22 +21,49 @@
2121
22 <div tal:condition="not: view/success">22 <div tal:condition="not: view/success">
2323
24 <div class="error" tal:condition="view/errortext"24 <tal:block condition="view/errortext">
25 tal:content="structure view/errortext" />25 <h1>Your password reset request was unsuccessful</h1>
26 <p class="error message"
27 style="margin-top: 2em;"
28 tal:content="structure view/errortext" />
29 </tal:block>
2630
27 <p>31 <p>
28 Enter your e-mail address, and we will send you instructions32 Enter your e-mail address, and we will send you instructions
29 on how to reset your password. 33 on how to reset your password. You'll also need to answer a simple
34 question so we know that you're human.
30 </p>35 </p>
3136
32 <form name="forgottenpassword" method="POST">37 <form name="forgottenpassword" method="POST">
38 <input type="hidden"
39 tal:attributes="name view/captcha_hash;
40 value view/get_captcha_hash" />
33 <div class="field">41 <div class="field">
34 <label for="email">Email address:</label>42 <table>
35 <input type="text" size="50" name="email" />43 <tr>
44 <th>
45 <label for="email">Email address:</label>
46 </th>
47 <td>
48 <input type="text" size="50" name="email" />
49 </td>
50 </tr>
51 <tr>
52 <th>
53 <label>Random question:</label>
54 </th>
55 <td>
56 <span id="problem" tal:content="view/captcha_problem" />
57 <input type="text" size="5"
58 id="captcha_submission" value=""
59 tal:attributes="name view/captcha_submission"
60 />
61 </td>
62 </tr>
63 </table>
36 <div class="formHelp">64 <div class="formHelp">
37 <p>65 <p>
38 This should be the email address registered for your Launchpad66 Enter the email address registered for your Launchpad account.
39 account.
40 </p>67 </p>
41 <p>68 <p>
42 If you are claiming an existing account but don't know the email69 If you are claiming an existing account but don't know the email
4370
=== modified file 'lib/canonical/launchpad/templates/launchpad-login.pt'
--- lib/canonical/launchpad/templates/launchpad-login.pt 2009-10-08 15:57:01 +0000
+++ lib/canonical/launchpad/templates/launchpad-login.pt 2009-10-16 19:59:17 +0000
@@ -164,6 +164,7 @@
164 <tal:block condition="view/registration_error">164 <tal:block condition="view/registration_error">
165 <h1>Your registration was unsuccessful</h1>165 <h1>Your registration was unsuccessful</h1>
166 <p class="error message"166 <p class="error message"
167 style="margin-top: 2em;"
167 tal:content="structure view/registration_error" />168 tal:content="structure view/registration_error" />
168 </tal:block>169 </tal:block>
169 <!-- 5. might want to register: -->170 <!-- 5. might want to register: -->
170171
=== modified file 'lib/canonical/launchpad/webapp/login.py'
--- lib/canonical/launchpad/webapp/login.py 2009-10-08 15:57:01 +0000
+++ lib/canonical/launchpad/webapp/login.py 2009-10-16 19:59:17 +0000
@@ -153,8 +153,44 @@
153 return getUtility(IPersonSet).getByName(153 return getUtility(IPersonSet).getByName(
154 config.launchpad.restrict_to_team).title154 config.launchpad.restrict_to_team).title
155155
156156class CaptchaMixin:
157class LoginOrRegister:157 """Mixin class to provide simple captcha capabilities."""
158 def validateCaptcha(self):
159 """Validate the submitted captcha value matches what we expect."""
160 expected = self.request.form.get(self.captcha_hash)
161 submitted = self.request.form.get(self.captcha_submission)
162 if expected is not None and submitted is not None:
163 return md5.new(submitted).hexdigest() == expected
164 return False
165
166 @cachedproperty
167 def captcha_answer(self):
168 """Get the answer for the current captcha challenge.
169
170 With each failed attempt a new challenge will be given. Our answer
171 space is acknowledged to be ridiculously small but is chosen in the
172 interest of ease-of-use. We're not trying to create an iron-clad
173 challenge but only a minimal obstacle to dumb bots.
174 """
175 return random.randint(10, 20)
176
177 @property
178 def get_captcha_hash(self):
179 """Get the captcha hash.
180
181 The hash is the value we put in the form for later comparison.
182 """
183 return md5.new(str(self.captcha_answer)).hexdigest()
184
185 @property
186 def captcha_problem(self):
187 """Create the captcha challenge."""
188 op1 = random.randint(1, self.captcha_answer - 1)
189 op2 = self.captcha_answer - op1
190 return '%d + %d =' % (op1, op2)
191
192
193class LoginOrRegister(CaptchaMixin):
158 """Merges the former CookieLoginPage and JoinLaunchpadView classes194 """Merges the former CookieLoginPage and JoinLaunchpadView classes
159 to allow the two forms to appear on a single page.195 to allow the two forms to appear on a single page.
160196
@@ -399,41 +435,6 @@
399 L.append(html % (name, cgi.escape(value, quote=True)))435 L.append(html % (name, cgi.escape(value, quote=True)))
400 return '\n'.join(L)436 return '\n'.join(L)
401437
402 def validateCaptcha(self):
403 """Validate the submitted captcha value matches what we expect."""
404 expected = self.request.form.get(self.captcha_hash)
405 submitted = self.request.form.get(self.captcha_submission)
406 if expected is not None and submitted is not None:
407 return md5.new(submitted).hexdigest() == expected
408 return False
409
410 @cachedproperty
411 def captcha_answer(self):
412 """Get the answer for the current captcha challenge.
413
414 With each failed attempt a new challenge will be given. Our answer
415 space is acknowledged to be ridiculously small but is chosen in the
416 interest of ease-of-use. We're not trying to create an iron-clad
417 challenge but only a minimal obstacle to dumb bots.
418 """
419 return random.randint(10, 20)
420
421 @property
422 def get_captcha_hash(self):
423 """Get the captcha hash.
424
425 The hash is the value we put in the form for later comparison.
426 """
427 return md5.new(str(self.captcha_answer)).hexdigest()
428
429 @property
430 def captcha_problem(self):
431 """Create the captcha challenge."""
432 op1 = random.randint(1, self.captcha_answer)
433 op2 = self.captcha_answer - op1
434 return '%d + %d =' % (op1, op2)
435
436
437def logInPrincipal(request, principal, email):438def logInPrincipal(request, principal, email):
438 """Log the principal in. Password validation must be done in callsites."""439 """Log the principal in. Password validation must be done in callsites."""
439 session = ISession(request)440 session = ISession(request)
@@ -542,16 +543,25 @@
542 return ''543 return ''
543544
544545
545class ForgottenPasswordPage:546class ForgottenPasswordPage(CaptchaMixin):
546547
547 errortext = None548 errortext = None
548 submitted = False549 submitted = False
550 captcha_submission = 'captcha_submission'
551 captcha_hash = 'captcha_hash'
549552
550 def process_form(self):553 def process_form(self):
551 request = self.request554 request = self.request
552 if request.method != "POST":555 if request.method != "POST":
553 return556 return
554557
558 # Validate the user is human, more or less.
559 if not self.validateCaptcha():
560 self.errortext = (
561 "The answer to the simple math question was incorrect "
562 "or missing. Please try again.")
563 return
564
555 email = request.form.get("email").strip()565 email = request.form.get("email").strip()
556 person = getUtility(IPersonSet).getByEmail(email)566 person = getUtility(IPersonSet).getByEmail(email)
557 if person is None:567 if person is None:
558568
=== modified file 'lib/lp/registry/stories/foaf/xx-createaccount.txt'
--- lib/lp/registry/stories/foaf/xx-createaccount.txt 2009-10-08 19:52:06 +0000
+++ lib/lp/registry/stories/foaf/xx-createaccount.txt 2009-10-16 19:59:17 +0000
@@ -36,7 +36,7 @@
36email address from before has been retained in the form.36email address from before has been retained in the form.
3737
38 >>> from lp.testing.registration import set_captcha_answer38 >>> from lp.testing.registration import set_captcha_answer
39 >>> set_captcha_answer(browser)39 >>> set_captcha_answer(browser, prefix='loginpage_')
40 >>> browser.getControl('Register').click()40 >>> browser.getControl('Register').click()
41 >>> print_feedback_messages(browser.contents)41 >>> print_feedback_messages(browser.contents)
42 >>> print extract_text(find_main_content(browser.contents))42 >>> print extract_text(find_main_content(browser.contents))
@@ -103,7 +103,7 @@
103 >>> browser.open('http://launchpad.dev/+login')103 >>> browser.open('http://launchpad.dev/+login')
104 >>> browser.getControl(name='loginpage_email', index=1).value = (104 >>> browser.getControl(name='loginpage_email', index=1).value = (
105 ... 'jperson@example.com')105 ... 'jperson@example.com')
106 >>> set_captcha_answer(browser)106 >>> set_captcha_answer(browser, prefix='loginpage_')
107 >>> browser.getControl('Register').click()107 >>> browser.getControl('Register').click()
108108
109 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()109 >>> from_addr, to_addrs, raw_msg = stub.test_emails.pop()
110110
=== modified file 'lib/lp/registry/stories/foaf/xx-reg-with-existing-email.txt'
--- lib/lp/registry/stories/foaf/xx-reg-with-existing-email.txt 2009-10-08 19:52:06 +0000
+++ lib/lp/registry/stories/foaf/xx-reg-with-existing-email.txt 2009-10-16 19:59:17 +0000
@@ -16,7 +16,7 @@
16 >>> browser.getControl('E-mail address:', index=1).value = (16 >>> browser.getControl('E-mail address:', index=1).value = (
17 ... 'test@canonical.com')17 ... 'test@canonical.com')
18 >>> from lp.testing.registration import set_captcha_answer18 >>> from lp.testing.registration import set_captcha_answer
19 >>> set_captcha_answer(browser)19 >>> set_captcha_answer(browser, prefix='loginpage_')
20 >>> browser.getControl('Register').click()20 >>> browser.getControl('Register').click()
2121
22 >>> for message in get_feedback_messages(browser.contents):22 >>> for message in get_feedback_messages(browser.contents):
@@ -33,7 +33,7 @@
33on with the registration process.33on with the registration process.
3434
35 >>> browser.getControl('E-mail address:', index=1).value = 'mpo@iki.fi'35 >>> browser.getControl('E-mail address:', index=1).value = 'mpo@iki.fi'
36 >>> set_captcha_answer(browser)36 >>> set_captcha_answer(browser, prefix='loginpage_')
37 >>> browser.getControl('Register').click()37 >>> browser.getControl('Register').click()
3838
39 >>> print extract_text(find_tag_by_id(browser.contents, 'address'))39 >>> print extract_text(find_tag_by_id(browser.contents, 'address'))
@@ -85,7 +85,7 @@
8585
86 >>> anon_browser.getControl('E-mail address:', index=1).value = (86 >>> anon_browser.getControl('E-mail address:', index=1).value = (
87 ... 'christian.reis@ubuntulinux.com')87 ... 'christian.reis@ubuntulinux.com')
88 >>> set_captcha_answer(anon_browser)88 >>> set_captcha_answer(anon_browser, prefix='loginpage_')
89 >>> anon_browser.getControl('Register').click()89 >>> anon_browser.getControl('Register').click()
9090
91 >>> from_addr, to_addr, msg = stub.test_emails.pop()91 >>> from_addr, to_addr, msg = stub.test_emails.pop()
@@ -147,7 +147,7 @@
147147
148 >>> browser.getControl('E-mail address:', index=1).value = (148 >>> browser.getControl('E-mail address:', index=1).value = (
149 ... 'bad-user@canonical.com')149 ... 'bad-user@canonical.com')
150 >>> set_captcha_answer(browser)150 >>> set_captcha_answer(browser, prefix='loginpage_')
151 >>> browser.getControl('Register').click()151 >>> browser.getControl('Register').click()
152152
153 >>> print extract_text(find_main_content(browser.contents).p)153 >>> print extract_text(find_main_content(browser.contents).p)
154154
=== modified file 'lib/lp/registry/stories/foaf/xx-resetpassword.txt'
--- lib/lp/registry/stories/foaf/xx-resetpassword.txt 2009-09-18 01:09:10 +0000
+++ lib/lp/registry/stories/foaf/xx-resetpassword.txt 2009-10-16 19:59:17 +0000
@@ -15,10 +15,12 @@
15 >>> browser.title15 >>> browser.title
16 'Need a new Launchpad password?'16 'Need a new Launchpad password?'
1717
18He types the email address registered in Launchpad and submit the form.18He types the email address registered in Launchpad and submits the form.
1919
20 >>> from lp.testing.registration import set_captcha_answer
20 >>> browser.getControl(name='email').value = (21 >>> browser.getControl(name='email').value = (
21 ... 'david.allouche@canonical.com')22 ... 'david.allouche@canonical.com')
23 >>> set_captcha_answer(browser)
22 >>> browser.getControl('Request Reset').click()24 >>> browser.getControl('Request Reset').click()
23 >>> print extract_text(find_main_content(browser.contents))25 >>> print extract_text(find_main_content(browser.contents))
24 Need a new Launchpad password?26 Need a new Launchpad password?
@@ -68,8 +70,7 @@
68 >>> browser.url70 >>> browser.url
69 'http://launchpad.dev/token/.../+resetpassword'71 'http://launchpad.dev/token/.../+resetpassword'
7072
71 >>> for tag in get_feedback_messages(browser.contents):73 >>> print_feedback_messages(browser.contents)
72 ... print extract_text(tag)
73 There is 1 error.74 There is 1 error.
74 The password provided contains non-ASCII characters.75 The password provided contains non-ASCII characters.
7576
@@ -82,8 +83,7 @@
82 >>> browser.url83 >>> browser.url
83 'http://launchpad.dev'84 'http://launchpad.dev'
8485
85 >>> for tag in get_feedback_messages(browser.contents):86 >>> print_feedback_messages(browser.contents)
86 ... print extract_text(tag)
87 Your password has been reset successfully.87 Your password has been reset successfully.
8888
89 >>> print extract_text(find_tag_by_id(browser.contents, 'logincontrol'))89 >>> print extract_text(find_tag_by_id(browser.contents, 'logincontrol'))
@@ -104,8 +104,7 @@
104logs out and then logs in again:104logs out and then logs in again:
105105
106 >>> browser.open('http://launchpad.dev/+logout')106 >>> browser.open('http://launchpad.dev/+logout')
107 >>> for tag in get_feedback_messages(browser.contents):107 >>> print_feedback_messages(browser.contents)
108 ... print extract_text(tag)
109 You have been logged out108 You have been logged out
110109
111 >>> browser.getLink('Log in / Register').click()110 >>> browser.getLink('Log in / Register').click()
@@ -122,6 +121,20 @@
122 David Allouche...121 David Allouche...
123122
124123
124== Reset is protected by a math captcha ==
125
126When requesting a password reset David is presented with a simple math
127problem which must be solved correctly before proceeding.
128
129 >>> browser.open('http://launchpad.dev/+forgottenpassword')
130 >>> browser.getControl(name='email').value = (
131 ... 'david.allouche@canonical.com')
132 >>> browser.getControl(name='captcha_submission').value = '-1'
133 >>> browser.getControl('Request Reset').click()
134 >>> print_feedback_messages(browser.contents)
135 The answer to the simple math question was incorrect or missing. Please try again.
136
137
125== Using email addresses other than the preferred one ==138== Using email addresses other than the preferred one ==
126139
127Any of a person's validated email addresses can be used to reset his140Any of a person's validated email addresses can be used to reset his
@@ -134,8 +147,9 @@
134 >>> browser.open('http://launchpad.dev/+login')147 >>> browser.open('http://launchpad.dev/+login')
135 >>> browser.getLink('Forgotten your password?').click()148 >>> browser.getLink('Forgotten your password?').click()
136 >>> browser.getControl(name='email').value = 'david@canonical.com'149 >>> browser.getControl(name='email').value = 'david@canonical.com'
150 >>> set_captcha_answer(browser)
137 >>> browser.getControl('Request Reset').click()151 >>> browser.getControl('Request Reset').click()
138 >>> print "\n".join(get_feedback_messages(browser.contents))152 >>> print_feedback_messages(browser.contents)
139153
140He follows the link sent to his email just like he did before.154He follows the link sent to his email just like he did before.
141155
@@ -155,7 +169,7 @@
155 >>> browser.getControl('Continue').click()169 >>> browser.getControl('Continue').click()
156 >>> browser.url170 >>> browser.url
157 'http://launchpad.dev'171 'http://launchpad.dev'
158 >>> print "\n".join(get_feedback_messages(browser.contents))172 >>> print_feedback_messages(browser.contents)
159 Your password has been reset successfully.173 Your password has been reset successfully.
160174
161175
@@ -165,12 +179,11 @@
165179
166 >>> browser.open('http://launchpad.dev/+forgottenpassword')180 >>> browser.open('http://launchpad.dev/+forgottenpassword')
167 >>> browser.getControl(name='email').value = 'support@ubuntu.com'181 >>> browser.getControl(name='email').value = 'support@ubuntu.com'
182 >>> set_captcha_answer(browser)
168 >>> browser.getControl('Request Reset').click()183 >>> browser.getControl('Request Reset').click()
169184 >>> print_feedback_messages(browser.contents)
170 >>> for tag in find_tags_by_class(browser.contents, 'error'):185 The email address support@ubuntu.com
171 ... print tag186 belongs to a team, and teams cannot log in to Launchpad.
172 <div class="error">The email address <strong>support@ubuntu.com</strong>
173 belongs to a team, and teams cannot log in to Launchpad.</div>
174187
175188
176== Reactivating an account ==189== Reactivating an account ==
@@ -202,8 +215,7 @@
202 >>> browser.title215 >>> browser.title
203 'Log in or register with Launchpad'216 'Log in or register with Launchpad'
204217
205 >>> for tag in get_feedback_messages(browser.contents):218 >>> print_feedback_messages(browser.contents)
206 ... print extract_text(tag)
207 The email address belongs to a deactivated account. Use the219 The email address belongs to a deactivated account. Use the
208 "Forgotten your password" link to reactivate it.220 "Forgotten your password" link to reactivate it.
209221
@@ -215,6 +227,7 @@
215227
216 >>> browser.getControl(name='email').value = (228 >>> browser.getControl(name='email').value = (
217 ... 'former-user@canonical.com')229 ... 'former-user@canonical.com')
230 >>> set_captcha_answer(browser)
218 >>> browser.getControl('Request Reset').click()231 >>> browser.getControl('Request Reset').click()
219232
220 >>> print extract_text(find_main_content(browser.contents).p)233 >>> print extract_text(find_main_content(browser.contents).p)
@@ -248,8 +261,7 @@
248 >>> browser.url261 >>> browser.url
249 'http://launchpad.dev'262 'http://launchpad.dev'
250263
251 >>> for tag in get_feedback_messages(browser.contents):264 >>> print_feedback_messages(browser.contents)
252 ... print extract_text(tag)
253 Welcome back to Launchpad.265 Welcome back to Launchpad.
254 Your password has been reset successfully.266 Your password has been reset successfully.
255267
@@ -300,6 +312,7 @@
300 'Need a new Launchpad password?'312 'Need a new Launchpad password?'
301 >>> browser.getControl(name='email').value = (313 >>> browser.getControl(name='email').value = (
302 ... 'bad-user@canonical.com')314 ... 'bad-user@canonical.com')
315 >>> set_captcha_answer(browser)
303 >>> browser.getControl('Request Reset').click()316 >>> browser.getControl('Request Reset').click()
304317
305 >>> print extract_text(find_main_content(browser.contents).p)318 >>> print extract_text(find_main_content(browser.contents).p)
@@ -330,9 +343,6 @@
330 >>> browser.getControl(name='field.password_dupe').value = 'test'343 >>> browser.getControl(name='field.password_dupe').value = 'test'
331 >>> browser.getControl('Continue').click()344 >>> browser.getControl('Continue').click()
332345
333 >>> for tag in find_tags_by_class(346 >>> print_feedback_messages(browser.contents)
334 ... browser.contents, 'warning'):347 Your password cannot be reset because your account is suspended.
335 ... print tag348 Contact a Launchpad admin about this issue.
336 <div ...>Your password cannot be reset because your account is suspended.
337 Contact a <a href="mailto:feedback@launchpad.net?subject=SU...">Launchpad
338 admin</a> about this issue.</div>
339349
=== modified file 'lib/lp/testing/registration.py'
--- lib/lp/testing/registration.py 2009-10-08 20:22:25 +0000
+++ lib/lp/testing/registration.py 2009-10-16 19:59:17 +0000
@@ -24,9 +24,9 @@
24 return ''24 return ''
2525
2626
27def set_captcha_answer(browser, answer=None):27def set_captcha_answer(browser, answer=None, prefix=''):
28 """Given a browser, set the login captcha with the correct answer."""28 """Given a browser, set the login captcha with the correct answer."""
29 if answer is None:29 if answer is None:
30 answer = get_captcha_answer(browser.contents)30 answer = get_captcha_answer(browser.contents)
31 browser.getControl(name='loginpage_captcha_submission').value = (31 browser.getControl(name=prefix + 'captcha_submission').value = (
32 answer)32 answer)