Functional tests for PasswordResetTool
======================================

Introduction
------------

Note that our usage of testbrowser is unusual and inconsistent, mostly
because Plone forms have inconsistencies and because testbrowser makes
assumptions that are not true for Plone forms.

  >>> from Products.PloneTestCase import PloneTestCase as PTC
  >>> from Products.Five.testbrowser import Browser
  >>> browser = Browser()
  >>> browser.handleErrors = False
  >>> browser.open('http://nohost/plone/')


Assumptions
-----------

First of all we have to be aware that Plone by default implements two
distinct password policies regarding member registration.

  A. Users can provide their own password (and can optionally be send
     an e-mail with login credentials) and can login directly without
     validation of the e-mail address.

  B. A password is generated for the users (and an e-mail with login
     credentials is sent automatically).

This policy can enabled or disabled in the ``validate_email`` property
on the Plone Site object.  By default ``validate_email`` is enabled
and the second policy applies.

Another aspect we have to take into account is the fact that Plone by
default only allows Administrators to register (other) members, but allowing
users to register themselves can be enabled.

Users of PasswordResetTool don't want any credentials to be sent out
via e-mail.  Instead, PasswordResetTool sends out an e-mail containing
an URL where the user can set their password.

The PasswordResetTool has to respect both policies (A and B) and both
use cases (Anonymous or Admin?).  The desired result after installing
PasswordResetTool is as follows:

  1. Anonymous user registers himself

    A. Users can provide their own password during registration, but
       don't have the option to send credentials by e-mail.

    B. Users can't provide a password but are sent an e-mail with a
       link to set their password.  (Validates e-mail address.)

  2. Site Admin registers a user

    A. The Site Admin can provide a password.  He is not allowed to
       send the credentials via e-mail.

    B. The Site Admin can't provide a password.  Instead, Plone will
       send the registered user an e-mail with a link to set the
       password.  (Validates e-mail address.)

In addition, we want users to be logged in directly whenever possible.
E.g., whenever a user enters his credentials he should not be asked
for it again on the next page.


1A. User joins and forgets password
-----------------------------------

What we do here:

  - Join the portal
  - Log in
  - Log out again
  - Forget our password (this is where PasswordResetTool comes in)
  - Read the e-mail that contains the URL we visit to reset our password
  - Reset our password
  - Log in with our new password

Let's join as a new user. Plone's default settings won't let the user
type in his initial password, so we need to enable that:

  >>> browser.open('http://nohost/plone/login')
  >>> browser.getLink('Log in').click()
  >>> browser.getControl(name='__ac_name').value = PTC.portal_owner
  >>> browser.getControl(name='__ac_password').value = PTC.default_password
  >>> browser.getControl(name='submit').click()
  >>> "You are now logged in" in browser.contents
  True

Let's go directly to the security control panel:

  >>> browser.open('http://nohost/plone/@@security-controlpanel')
  >>> ctrl = browser.getControl(name="form.enable_self_reg")
  >>> ctrl.value = "on"
  >>> ctrl = browser.getControl(name="form.enable_user_pwd_choice")
  >>> ctrl.value = "on"
  >>> browser.getControl(name="form.actions.save").click()

Log out again and then join:

  >>> browser.getLink('Log out').click()
  >>> "You are now logged out" in browser.contents
  True
  >>> 'New user?' in browser.contents  # Sunburst theme has no Register link
  True

Now register a new user:

  >>> browser.open('http://nohost/plone/@@register')
  >>> browser.url
  'http://nohost/plone/@@register'

  >>> browser.getControl(name='form.username').value = 'jsmith'
  >>> browser.getControl(name='form.email').value = 'jsmith@example.com'
  >>> browser.getControl(name='form.password').value = 'secret'
  >>> browser.getControl(name='form.password_ctl').value = 'secret'
  >>> browser.getControl(name='form.actions.register').click()

XXX Make sure we don't have a way to receive our credentials via
e-mail.

  >>> "You have been registered" in browser.contents
  True

We are not logged in yet at this point.  Let's try to log in:

  >>> browser.getLink('Log in').click()
  >>> browser.url.startswith('http://nohost/plone/login')
  True
  >>> browser.getControl(name='__ac_name').value = 'jsmith'
  >>> browser.getControl(name='__ac_password').value = 'secret'
  >>> browser.getControl(name='submit').click()
  >>> "You are now logged in" in browser.contents
  True

Log out again:

  >>> browser.getLink('Log out').click()
  >>> "You are now logged out" in browser.contents
  True

Now it is time to forget our password and click the ``Forgot your
password`` in the login form:

  >>> browser.open('http://nohost/plone/login')
  >>> browser.getLink('we can send you a new one').click()
  >>> browser.url.startswith('http://nohost/plone/mail_password_form')
  True
  >>> form = browser.getForm(name='mail_password')
  >>> form.getControl(name='userid').value = 'jsmith'
  >>> form.submit()

As part of our test setup, we replaced the original MailHost with our
own version.  Our version doesn't mail messages, it just collects them
in a list called ``messages``:

  >>> mailhost = self.portal.MailHost
  >>> len(mailhost.messages)
  1
  >>> msg = mailhost.messages[0]

Now that we have the message, we want to look at its contents, and
then we extract the address that lets us reset our password:

  >>> "To: jsmith@example.com" in msg
  True
  >>> please_visit_text = "The following link will take you to a page where you can reset your passwor=\nd for Plone site site:\n    =\n\n"
  >>> please_visit_text in msg
  True
  >>> url_index = msg.index(please_visit_text) + len(please_visit_text)
  >>> address = msg[url_index:].split()[0]
  >>> address # doctest: +ELLIPSIS
  'http://nohost/plone/passwordreset/...'

Now that we have the address, we will reset our password:

  >>> browser.open(address)
  >>> "Set your password" in browser.contents
  True

  >>> form = browser.getForm(name='pwreset_action')
  >>> form.getControl(name='userid').value = 'jsmith'
  >>> form.getControl(name='password').value = 'secretion'
  >>> form.getControl(name='password2').value = 'secretion'
  >>> form.submit()

We can now login using our new password:

  >>> "Your password has been set successfully." in browser.contents
  True
  >>> browser.url
  'http://nohost/plone/pwreset_finish'
  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl(name='__ac_name').value = 'jsmith'
  >>> browser.getControl(name='__ac_password').value = 'secretion'
  >>> browser.getControl(name='submit').click()

We should be logged in now:

  >>> "You are now logged in" in browser.contents
  True

Log out again:

  >>> browser.getLink('Log out').click()
  >>> "You are now logged out" in browser.contents
  True


2A. Administrator registers user
--------------------------------

  - Log in as the portal owner
  - Browse to User and Group Management and add user
  - Register a member (with send email checked???)
  - Log out
  - Log in as the new member

First, we want to login as the portal owner:

  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl(name='__ac_name').value = PTC.portal_owner
  >>> browser.getControl(name='__ac_password').value = PTC.default_password
  >>> browser.getControl(name='submit').click()
  >>> "You are now logged in" in browser.contents
  True

We navigate to the Users Overview page and register a new user:

  >>> browser.getLink('Site Setup').click()
  >>> browser.getLink('Users and Groups').click()
  >>> browser.getControl('Add New User').click()
  >>> browser.url
  'http://nohost/plone/@@new-user'

  >>> browser.getControl(name='form.username').value = 'wsmith'
  >>> browser.getControl(name='form.email').value = 'wsmith@example.com'
  >>> browser.getControl(name='form.password').value = 'supersecret'
  >>> browser.getControl(name='form.password_ctl').value = 'supersecret'
  >>> browser.getControl(name='form.actions.register').click()
  >>> 'User added.' in browser.contents
  True

XXX Make sure we don't have a way to send the credentials via e-mail.

We want to logout and login as the new member:

  >>> browser.getLink('Log out').click()
  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl(name='__ac_name').value = 'wsmith'
  >>> browser.getControl(name='__ac_password').value = 'supersecret'
  >>> browser.getControl(name='submit').click()
  >>> "You are now logged in" in browser.contents
  True

  >>> browser.getLink('Log out').click()


1B. User joins with e-mail validation enabled and forgets password
------------------------------------------------------------------

What we do here is quite similiar to 1A, but instead of typing in the
password ourselves, we will be sent an e-mail with the URL to set our
password.

First off, we need to set ``validate_mail`` to False:

  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl(name='__ac_name').value = PTC.portal_owner
  >>> browser.getControl(name='__ac_password').value = PTC.default_password
  >>> browser.getControl(name='submit').click()
  >>> "You are now logged in" in browser.contents
  True

Let's go directly to the security control panel:

  >>> browser.open('http://nohost/plone/@@security-controlpanel')
  >>> ctrl = browser.getControl("Let users select their own passwords")
  >>> ctrl.selected = False
  >>> browser.getControl('Save').click()

Log out again and then join:

  >>> browser.getLink('Log out').click()
  >>> "You are now logged out" in browser.contents
  True
  >>> browser.open('http://nohost/plone/@@register')
  >>> browser.getControl(name='form.username').value = 'bsmith'
  >>> browser.getControl(name='form.email').value = 'bsmith@example.com'

We shouldn't be able to fill in our password:

  >>> browser.getControl(name='form.password').value = 'secret' # doctest: +ELLIPSIS
  Traceback (most recent call last):
  ...
  LookupError: name 'form.password'

Now register:

  >>> browser.getControl(name='form.actions.register').click()
  >>> "You have been registered" in browser.contents
  True

We should have received an e-mail at this point:

  >>> mailhost = self.portal.MailHost
  >>> len(mailhost.messages)
  2
  >>> msg = str(mailhost.messages[-1])

Now that we have the message, we want to look at its contents, and
then we extract the address that lets us reset our password:

  >>> from email.parser import Parser
  >>> import quopri
  >>> import re
  >>> parser = Parser()
  >>> message = parser.parsestr(msg)
  >>> message["To"]
  'bsmith@example.com'
  >>> msgtext = quopri.decodestring(message.get_payload())
  >>> "Please activate it by visiting" in msgtext
  True
  >>> address = re.search('(http://nohost/plone/passwordreset/[a-z0-9]+\?userid=.*)\s', msgtext).groups()[0]

Now that we have the address, we will reset our password:

  >>> browser.open(address)
  >>> "Please fill out the form below to set your password" in browser.contents
  True
  >>> browser.getControl(name='password').value = 'secret'
  >>> browser.getControl(name='password2').value = 'secret'
  >>> browser.getControl("Set my password").click()
  >>> "Your password has been set successfully." in browser.contents
  True

Now we can log in:

  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl("Login Name").value = 'bsmith'
  >>> browser.getControl("Password").value = 'secret'
  >>> browser.getControl("Log in").click()
  >>> "You are now logged in" in browser.contents
  True

Log out again:

  >>> browser.getLink('Log out').click()
  >>> "You are now logged out" in browser.contents
  True


2B. Administrator adds user with email validation enabled
---------------------------------------------------------

Simliar to 2A, but instead of setting the password for new member, an
e-mail is sent containing the URL that lets the user log in.

First, we want to login as the portal owner:

  >>> from Products.PloneTestCase import PloneTestCase as PTC
  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl(name='__ac_name').value = PTC.portal_owner
  >>> browser.getControl(name='__ac_password').value = PTC.default_password
  >>> browser.getControl(name='submit').click()
  >>> "You are now logged in" in browser.contents
  True

We navigate to the Users Overview page and register a new user:

  >>> browser.getLink('Site Setup').click()
  >>> browser.getLink('Users and Groups').click()
  >>> browser.getControl('Add New User').click()
  >>> browser.url
  'http://nohost/plone/@@new-user'

  >>> browser.getControl(name='form.username').value = 'wwwsmith'
  >>> browser.getControl(name='form.email').value = 'wwwsmith@example.com'
  >>> browser.getControl(name='form.password').value = 'secret'
  >>> browser.getControl(name='form.password_ctl').value = 'secret'
  >>> browser.getControl('Send a confirmation mail with a link to set the password').selected = True

Now register and logout:

  >>> browser.getControl(name='form.actions.register').click()
  >>> browser.getLink('Log out').click()
  >>> "You are now logged out" in browser.contents
  True

We should have received an e-mail at this point:

  >>> mailhost = self.portal.MailHost
  >>> len(mailhost.messages)
  3
  >>> msg = str(mailhost.messages[-1])

Now that we have the message, we want to look at its contents, and
then we extract the address that lets us reset our password:

  >>> message = parser.parsestr(msg)
  >>> message["To"]
  'wwwsmith@example.com'
  >>> msgtext = quopri.decodestring(message.get_payload())
  >>> "Please activate it by visiting" in msgtext
  True
  >>> address = re.search('(http://nohost/plone/passwordreset/[a-z0-9]+\?userid=.*)\s', msgtext).groups()[0]

Now that we have the address, we will reset our password:

  >>> browser.open(address)
  >>> "Please fill out the form below to set your password" in browser.contents
  True
  >>> browser.getControl(name='password').value = 'superstr0ng'
  >>> browser.getControl(name='password2').value = 'superstr0ng'
  >>> browser.getControl("Set my password").click()
  >>> "Your password has been set successfully." in browser.contents
  True

Now we can log in:

  >>> browser.open('http://nohost/plone/login')
  >>> browser.getControl("Login Name").value = 'wwwsmith'
  >>> browser.getControl("Password").value = 'superstr0ng'
  >>> browser.getControl("Log in").click()
  >>> "You are now logged in" in browser.contents
  True

  >>> browser.getLink('Log out').click()
