Customizing Plone User Forms
============================

.. used in collective.developermanual


Custom Password Validation Plugins
----------------------------------

In some organisations it's important to ensure a greater set of constraints on Plone
passwords other than being >=5 chars. This is possible to implement by creating a new
IValidationPlugin of PluggableAuthenticationService which is included in Plone.

For instance, let's say you have the following method you want to use to validate
passwords.

  >>> def passwordvalidate(user, password):
  >>>   if password.count('dead'):
  >>>       return u'Must not be dead'

and you have the following hint text for when the user is first asked for a password.

  >>> PASSWORD_HELP = "Please enter a password that's alive"

To set these as the policy Plone uses for it's passwords you'll need to create a class
that implements IValidationPlugin which is designed to validate properties of an particular
user. Plone uses this to validate a new password for an existing user or new user.
Your validation plugin would look something like this.

  >>> from Products.PluggableAuthService.interfaces.plugins import IValidationPlugin
  >>> from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
  >>>
  >>> class MyPasswordPolicy(BasePlugin):
  >>>  meta_type = 'My Password Policy Plugin'
  >>>
  >>>  def validateUserInfo(self, user, set_id, set_info ):
  >>>
  >>>      errors = []
  >>>      if set_info and set_info.get('password', None) is not None:
  >>>          password = set_info['password']
  >>>          if password = '':
  >>>             # special case used to provide help text
  >>>             return PASSWORD_HELP
  >>>          error = passwordvalidate(user, password)
  >>>          if error:
  >>>              errors = [{'id':'password','error':error}]
  >>>          else:
  >>>              errors = []
  >>>      return errors


The validateUserInfo function will get used as follows
 - If no error is returned then password is accepted
 - If the password is '' then any errors produced will be used as help text
   on the field as to what password is expected. This will be added to the end of the
   password field description when registering or changing a password.
 - If the password is invalid return one or more error messages (should be translated).
   for instance [{'id':'password','error':'Longer password}]. These messages will be
   joined to any other errors from other password plugins to form the final error
   message to the user.


To use this plugin we will need to register this class with the PAS system.

  >>> from Products.PluggableAuthService.utils import classImplements
  >>> classImplements(MyPasswordPolicy,
  >>>              IValidationPlugin)

And then add the plugin into acl_users.

  >>> obj = MyPasswordPolicy('pw_pol')
  >>> portal = layer['portal']
  >>> portal.acl_users._setObject(obj.getId(), obj)
  >>> obj = portal.acl_users[obj.getId()]

Activate it

  >>> obj.manage_activateInterfaces(['IValidationPlugin'])

and deactivate the default 5 char password policy

  >>> for policy in portal.acl_users.objectIds(['Default Plone Password Policy']):
  >>>   portal.acl_users.plugins.deactivatePlugin(IValidationPlugin, policy)

Now our password policy is in force.

    >>> browser.open('http://nohost/plone/@@new-user')

Check that we are given our help text on what is a valid password

   >>> print(browser.contents)
    <...
    ...Enter your new password. Must not be dead...


We'll enter an invalid password

    Fill out the form.
    >>> browser.getControl('User Name').value = 'user5'
    >>> browser.getControl('E-mail').value = 'user5@example.com'
    >>> browser.getControl('Password').value = 'dead parrot'
    >>> browser.getControl('Confirm password').value = 'dead parrot'
    >>> browser.getControl('Register').click()

    >>> print(browser.contents)
    <...<div class="fieldErrorBox">Must not be dead</div>...
    >>> print(browser.url)
    http://...@@new-user...

Passwords that are autogenerated for users are not validated since they will never
be seen by the users. Users are instead sent a url via mail similar to the password
reset url which allows them to set their own password.
