==========================================================
Using the repoze.who LDAP plugins in your WSGI Application
==========================================================

Implementing authentication via `repoze.who` is a simple task that requires
few lines of code. So using its LDAP plugins should not be an exception: You
just have to configure `repoze.who` in your application and then add the
`repoze.who.plugins.ldap` plugin(s) you want to use in your application.


Setting up `repoze.who` with the LDAP authenticator
===================================================

This section explains how to setup `repoze.who` in order to use the LDAP plugins
in your WSGI application. It is based on `the documentation for repoze.who
<http://static.repoze.org/whodocs/>`_.

You can configure your authentication mechanism powered by `repoze.who` with
two methods: With an INI file or with Python code.

In the examples below we are only going to use the main plugin provided by this
package: The LDAP authenticator itself (:class:`LDAPAuthenticatorPlugin`). The
other plugins don't deal with authentication, but are useful to load automatically 
data related to the authenticated user from the LDAP server.

Using the `repoze.who` terminology, :class:`LDAPAuthenticatorPlugin` is an
`authenticator plugin` and the others are `metadata provider plugins`.


Configuring `repoze.who` in a INI file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can configure your `repoze.who` based authentication via a `*.ini` file,
and then load such settings in your application.

Say we have a file called `who.ini` with the following contents::

    # These contents have been adapted from:
    # http://static.repoze.org/whodocs/#middleware-configuration-via-config-file
    [plugin:form]
    use = repoze.who.plugins.form:make_plugin
    rememberer_name = auth_tkt

    [plugin:auth_tkt]
    use = repoze.who.plugins.auth_tkt:make_plugin
    secret = something

    [plugin:ldap_auth]
    use = repoze.who.plugins.ldap:LDAPAuthenticatorPlugin
    ldap_connection = ldap://ldap.yourcompany.com
    base_dn = ou=developers,dc=yourcompany,dc=com

    [general]
    request_classifier = repoze.who.classifiers:default_request_classifier
    challenge_decider = repoze.who.classifiers:default_challenge_decider

    [identifiers]
    plugins =
        form;browser
        auth_tkt

    [authenticators]
    plugins =
            ldap_auth

    [challengers]
    plugins =
        form;browser


With the settings above, authentication via `repoze.who` is configured this way:
Visitors will login with a form, providing their user name and password; then
these credentials will be checked against the LDAP server `ldap.yourcompany.com`
under `ou=developers,dc=yourcompany,dc=com`. This form will be displayed
when your WSGI application issues an HTTP *401* error.

For example, if an user enters `jsmith` as the user name and `valencia` as their
password, the LDAP authenticator will build their Distinguished Name (DN) as
`uid=jsmith,ou=developers,dc=yourcompany,dc=com` and will try to
authenticate them in the `ldap.yourcompany.com` LDAP server with this DN and
`valencia` as the password.

You may modify the way the DN is generated by subclassing
:class:`LDAPAuthenticatorPlugin` to override the `_get_dn` method.

Finally, you can load these settings by adding the `repoze.who` middleware to your
application::

    from repoze.who.config import make_middleware_with_config
    app_with_auth = make_middleware_with_config(app, '/path/to/who.ini')

In the documentation for `repoze.who` there is `a more detailed explanation
<http://static.repoze.org/whodocs/#middleware-configuration-via-config-file>`_
for the INI file method. 


Configuring `repoze.who` with Python code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The Python code below does the same as the INI file above::

    # This script has been adapted from
    # http://static.repoze.org/whodocs/#module-repoze.who.middleware
    
    # Importing the plugins to be used
    from repoze.who.interfaces import IIdentifier, IChallenger
    from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
    from repoze.who.plugins.form import FormPlugin
    from repoze.who.plugins.ldap import LDAPAuthenticatorPlugin

    # Configuring the plugins
    ldap_auth = LDAPAuthenticatorPlugin('ldap://ldap.yourcompany.com',
                                        'ou=developers,dc=yourcompany,dc=com')
    auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt')
    form = FormPlugin('__do_login', rememberer_name='auth_tkt')
    form.classifications = { IIdentifier: ['browser'],
                             IChallenger: ['browser'] } # only for browser
    identifiers = [('form', form),('auth_tkt',auth_tkt)]
    authenticators = [('ldap_auth', ldap_auth)]
    challengers = [('form',form)]
    mdproviders = []

    # Using the default repoze.who classifiers:
    from repoze.who.classifiers import default_request_classifier, \
                                       default_challenge_decider
    log_stream = None
    import os
    if os.environ.get('WHO_LOG'):
        log_stream = sys.stdout


Then you can get these settings applied by adding the `repoze.who` middleware
to your application::

    app_with_auth = PluggableAuthenticationMiddleware(
        app,
        identifiers,
        authenticators,
        challengers,
        mdproviders,
        default_request_classifier,
        default_challenge_decider,
        log_stream = log_stream,
        log_level = logging.DEBUG
        )

In the documentation for `repoze.who` there is `a detailed explanation
<http://static.repoze.org/whodocs/#module-repoze.who.middleware>`_ for this 
method. 


Framework-specific documentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You may want to check the following framework-specific documents to learn tips
on how to implement `repoze.who` in the framework you are using:

 * **Pylons**: `Authentication and Authorization with repoze.who
   <http://wiki.pylonshq.com/display/pylonscookbook/Authentication+and+Authorization+with+%60repoze.who%60>`_.
 * **TurboGears 2**: `Authentication and Authorization in TurboGears 2
   <http://www.turbogears.org/2.1/docs/main/Auth/index.html>`_
   (:mod:`repoze.who` is the default authentication library).


Using the LDAP plugins for repoze.who
=====================================

.. module:: repoze.who.plugins.ldap

Once you've setup `repoze.who`, you'll be ready to use its LDAP plugins. Below
you will find how to use them in your application. The
:mod:`repoze.who.plugins.ldap` module export two different **concrete**
authentication plugins, both derived from :class:`LDAPBaseAuthenticatorPlugin`.


.. class:: LDAPBaseAuthenticatorPlugin(ldap_connection, base_dn, naming_attribute='uid', returned_id='dn', start_tls=False, bind_dn='', bind_pass='')

    Any class derived from this one is in charge of the LDAP authentication 
    itself, using the LDAP connection object provided in the constructor
    (**ldap_connection**) -- which can be an LDAP URL or a
    :class:`ldap.ldapobject.SimpleLDAPObject` instance.

    By default, the returned user name will be the full DN; if your
    downstream WSGI application needs to use the bare login, you must set
    ``returned_id`` to ``'login'``.

    If the parameter `start_tls` is set to a `True` value, any communication
    with the directory server will be encrypted.

    If the parameters `bind_dn` and `bind_pass` are set to non-empty
    strings, before doing any operation, the plugin will try to bind with
    the server using the supplied credentials.

    This plugin and its subclasses are compatible with any `identifier plugin`
    that defines the `login` and `password` items in the `identity` dictionary
    (the `identifier plugins` provided by the built-in `repoze.who.plugins.form`
    plugin are some of them).

    It is a highly customizable plugin which can be adapted to your needs with
    no hassle. You could also include in the login form a `select` field for
    people to select the department they belong to, being the key of such
    departments the `Organizational Unit` in the LDAP server; then, in the
    **_get_dn** method you would get such value from the WSGI environment
    object (**environ**).
    
    Any derived class must set the way the DN is created by overriding
    the abstract *_get_dn* method. For example, say in your company 
    (with `dc=yourcompany,dc.com` as its DN) everybody belongs to 
    the `Organizational Unit` (OU) **employees** (`ou=employees`), except
    the shareholders who belong to the OU **shareholders** (`ou=shareholders`)::
    
        class YourCompanyLDAPAuthenticatorPlugin(LDAPBaseAuthenticatorPlugin):
            """Sample LDAP authenticator adapted to your company."""
            
            shareholders = ('lgarcia, 'mferreira', 'cnarea')
            """Set of shareholders of the company"""
            
            def _get_dn(self, environ, identity):
                try:
                    if identity['login'] in self.shareholders:
                        ou = 'shareholders'
                    else:
                        ou = 'employees'
                    return u'uid=%s,ou=%s,%s' % (identity['login'], ou,
                                                 self.base_dn)
                except (KeyError, TypeError):
                    raise ValueError('Could not find the DN from the identity '
                                     'and environment')
    
    It is possibly an useless example on how to customize the way the DN is
    found, but it's enough to show how to override it.
    
    If you're using a custom LDAP authenticator, as in the example above, you
    would have to change the `use` directive accordingly -- for example:
    
    .. code-block:: ini
        
        [plugin:ldap_auth]
        use = yourpackage.lib.auth:YourCompanyLDAPAuthenticatorPlugin
        ldap_connection = ldap://yourcompany.com
        base_dn = ou=employees,dc=yourcompany,dc=com

.. class:: LDAPAuthenticatorPlugin(ldap_connection, base_dn, naming_attribute='uid', returned_id='dn')

    This plugin connects to the specified LDAP server and tries to `bind` with the
    `Distinguished Name` (DN) made by joining the `login` in the `identity`
    dictionary as the naming attribute value and the **base_dn** specified in the
    constructor, and then it tries to bind with the `password` found in the
    `identity` dictionary; As a default, the used naming attribute is the 
    user id (`uid`).
    
    For example, if the `login` provided by the identifier is `carla` and the
    **base_dn** provided in the constructor is `ou=employees,dc=example,dc=org`,
    the resulting DN will be `uid=carla,ou=employees,dc=example,dc=org`.

    If the directory server's naming attribute were the `email` attribute,
    and we provided naming_attribute='email' in the constructor, the DN
    resulting for the identifier `carla@example.org` would be
    `email=carla@example.org,ou=employees,dc=example,dc=org`.

    To configure this plugin from an INI file, you'd have to include a section 
    like this::
    
        [plugin:ldap_auth]
        use = repoze.who.plugins.ldap:LDAPAuthenticatorPlugin
        ldap_connection = ldap://yourcompany.com
        base_dn = ou=employees,dc=yourcompany,dc=com
        naming_attribute = uid
        start_tls = True
    
.. class:: LDAPSearchAuthenticatorPlugin(ldap_connection, base_dn, naming_attribute='uid', search_scope='subtree', filterstr='', returned_id='dn')

    This plugin connects to the specified LDAP server and searches an entry
    residing below the **base_dn**, whose naming attribute's value is equal
    to the supplied login. If such an entry is found, it tries to bind as the
    entry's DN with the `password` found in the `identity` dictionary; As a 
    default, the used naming attribute is the user id (`uid`).

    The `search_scope` parameter in the constructor allows to choose whether
    to search the entry in the whole subtree below **base_dn**, or just on
    the level below if set as `search_scope='onelevel'`.

    For example, if the `login` provided by the identifier is `carla` and the
    **base_dn** provided in the constructor is `dc=example,dc=org`,
    with the default settings, the system could find the entry
    `uid=carla,ou=employees,dc=example,dc=org`; if we set 
    `search_scope='onelevel'`, the entry would not be found.

    If you would like to only allow some entries, you may setup a filter
    by means of the **filterstr** parameter, which is an string whose format is
    defined by `RFC 4515 - Lightweight Directory Access Protocol (LDAP): String 
    Representation of Search Filters <http://www.faqs.org/rfcs/rfc4515.html>`_.
    E.g. we can assert only person entries bearing a telephone number starting
    with `999111` can login by setting: 
    `filterstr='(&(objectClass=person)(telephoneNumber=999111*))'`
    in the constructor.

    To configure this plugin from an INI file, you'd have to include a section 
    like this:
    
    .. code-block:: ini
    
        [plugin:ldap_auth]
        use = repoze.who.plugins.ldap:LDAPSearchAuthenticatorPlugin
        ldap_connection = ldap://yourcompany.com
        base_dn = ou=employees,dc=yourcompany,dc=com
        naming_attribute = uid
        search_scope = subtree
        start_tls = True
    
    Finally, add the plugin to the set of authenticators::
    
        [authenticators]
        plugins =
                ldap_auth
    
    But if you're configuring `repoze.who` via Python code, you can use the code 
    below::
    
        ldap_auth = LDAPAuthenticatorPlugin('ldap://ldap.yourcompany.com',
                                            'ou=developers,dc=yourcompany,dc=com')
    or, respectively::
    
        ldap_auth = LDAPSearchAuthenticatorPlugin('ldap://ldap.yourcompany.com',
                                            'ou=developers,dc=yourcompany,dc=com')
    
    But if you're using a custom LDAP authenticator, you would have to use the
    code below instead::
    
        ldap_auth = YourCompanyLDAPAuthenticatorPlugin('ldap://ldap.yourcompany.com',
                                                       'ou=employees,dc=yourcompany,dc=com')
    
    Finally, add this authenticator to the set of authenticators::
    
        authenticators = [('ldap_auth', ldap_auth)]
    
    As in the example above.


.. class:: LDAPAttributesPlugin(ldap_connection[, attributes=None[, filterstr='(objectClass=*)']])

    This plugin enables you to load data for the authenticated user 
    automatically and have it available from the WSGI environment — in the
    `identity` dictionary, specifically.
    
    **ldap_connection** represents the connection to the LDAP server, which,
    as in :class:`LDAPAuthenticatorPlugin`, can be either an LDAP URL or an
    instance of `ldap.ldapobject.SimpleLDAPObject`. **attributes** represents
    the list of user's attributes that you would like to fetch from the LDAP
    server; it can be an iterable, an string where the attributes are separated
    by commas, or *None* to fetch all the available attributes.
    
    By default it loads the attributes available for *any* entry whose *DN* is
    the same as the one found by :class:`LDAPAuthenticatorPlugin`, which is
    desired in most situations.
    However, if you would like to exclude some entries, you may setup a filter
    by means of the **filterstr** parameter, which shares the same semantics
    as the **filterstr** parameter in :class:`LDAPSearchAuthenticatorPlugin`.

    There is no advanced usage for this plugin, and hopefully you would never 
    need to subclass it to suit your needs.
    
    To configure this plugin from an INI file, you'd have to include a section 
    like this::
    
        [plugin:ldap_attributes]
        use = repoze.who.plugins.ldap:LDAPAttributesPlugin
        ldap_connection = ldap://ldap.yourcompany.com
        attributes = cn,sn,mail
    
    If instead of loading the *Common Name*, *surname* and *email*, as with the
    settings above, you'd prefer to load all the available attributes for the
    authenticated user, you'd just have to remove the *attributes* directive.
    
    Finally, add the plugin to the set of metadata providers::
    
        [mdproviders]
        plugins =
                ldap_attributes
    
    But if you want to configure it via Python code, you can use the code 
    below::
    
        ldap_attributes = LDAPAttributesPlugin('ldap://ldap.yourcompany.com',
                                               ['cn', 'sn', 'email'])
    
    Again, if you would prefer to load all the available attributes for the 
    user, you just have to remove the second parameter.
    
    Finally, add this authenticator to the set of metadata providers in your 
    Python code::
    
        mdproviders = [('ldap_attributes', ldap_attributes)]

