Title: Writing a Custom Login/Authentication Handler
Status: Current
Revised: 18-SEP-2002


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

By default, GNUe clients look at a database connection, determine what
fields it needs in order to login (e.g., username and password), and then
asks its platform/interface dependent login handler to prompt the user for
this information. Once returned, the client connects to the database using
this information.

If needed, you can intercept a client's normal login handler to add your
own behavior.


Why?
----
Sometimes it is not enough to prompt for the "database" login.  Perhaps
you want finer control over logins, or simply need to authenticate against
something besides the database.

You might want to authenticate against an NIS source, LDAP, or some custom
source.  Maybe you have many users and want to authenticate against rows in
a database table and have the actual database login name/password be a
common one that no one knows; i.e., while users Jason and James might log
in using "jason" and "james", they might both connect to the database as
"commonuser".  Since you may not trust James, you only want him to know
that he is logging in as "james" and never know that he is being connected
as "commonuser".



What do I do?
-------------

First, a little explanation of how logins work:

1. A client needs to initialize a connection to a database, so it passes
   GNUe-Common a description of the database and asks for it to connect.

2. GNUe-Common looks at the database description, determines what values
   are needed to connect (usually, username and password).

3. GNUe-Common creates an instance of (or uses an existing instance of)
   gnue.common.GLoginHander.LoginHandler, or if the client provides a
   more advanced login handler (e.g., GNUe Form's graphical handler), an
   instance of this.

4. GNUe-Common calls LoginHandler.getLogin(), passing it basic information
   about the needed connection and a list of values that login handler
   should provide (i.e., '_username' & '_password').

5. GNUe-Common uses the results of the call to LoginHandler.getLogin() to
   create a connection to the database.


Now, if needed, you can create an Authenticator class that basically
intervenes in between steps #3-4 above and provide your own functionality.

If all goes well, your login handler will be used the next time you log in.

Some notes:

  1) Almost the only restriction placed on getLogin's functionality is
     that it must return a hash containing at least the values requested
     when getLogin was called, using the value id's supplied. Your code,
     in theory, can do whatever it needs in order to return these values.

     HOWEVER: Even though it is possible, it is NOT recommended that you
     try to write your code to prompt for the values.  It is HIGHLY
     recommended that you return values in the getLoginFields method so
     LoginHandler.getLogin can prompt for input.  The LoginHandler will
     know how to handle the current user's environment (i.e., should it
     display a GTK login box, generate HTML for a login box, not display
     a box at all, but prompt using good ol' fashioned text prompts?)
     It is quite extensible.  If you need to prompt for more fields than
     simply Username and Password, then simply add a definition to your
     requiredFields tuple and let the LoginHandler's getLogin prompt for 
     you.


There are three steps to adding a custom authenticator:

1. Create (or copy) a Python file that implements an Authenticator.
   An Authenticator contains a class called Authenticator, which has
   two methods, getRequiredFields() and login().  See the example below
   for details on what's expected from these two methods.

2. Place this file in either your Python search path, or in a
   path specified by ImportPath (in the [common] section of gnue.conf)

3. In your connections.conf file, add a custom_auth parameter that
   is the name of the file (without the path or .py extension):

     [myconn]
     adapter = psycopg
     host = localhost
     dbname = mydb
     custom_auth = MyPostgresAuthenticator


Creating a custom authenticator
===============================
A slightly more complicated example.  You have a table in one of your databases
called "users", that has a user and password field.

Your users will connect to the database using the username and password:
dbUser  and dbPassword.

You, however, want them to use their own username and password to be
authenticated, but after getting authenticated, the database only cares
about the real "dbUser" and "dbPassword".

File: MyPostgresAuthenticator.py
===============================================================================
import psycopg
from gnue.common.GConnections import LoginError

class Authenticator:
  #
  # getLoginFields is passed an list consisting of:
  #   Fields to Input:
  #      Attribute Name, Label/Description, Is Password?
  #
  #   This list is a list of the values the dbdriver
  #   expects to get back in order to login.
  #
  #   It should return a similarly formatted list that
  #   tells the LoginHandler what values need to be
  #   prompted for. (Typically, _username and _password)
  #   If nothing should be prompted (e.g., you have a
  #   certificate) then return () (an empty list).
  #
  def getLoginFields(self, dbRequiredFields):
    return dbRequiredFields

  #
  # Authenticate the user givem the values prompted for.
  # If the information is incorrect, then raise
  # gnue.common.GConnections.LoginError
  #
  # It should return a dictionary of {Attribute Name: Value}
  #
  def login(self, loginData):
    conn = psycopg.connect(user="theValidator",
                 password="hotmomma",
                 dbname="logins",
                 host="localhost"')
    cursor = conn.cursor()
    results = cursor.execute (
        'select 1 from users where username=%s and password=%s',
        (loginData['_username'],
         loginData['_password']) )
    if not cursor.fetchone():
      raise LoginError
    else:
      loginData['_username'] = 'dbLogin'
      loginData['_password'] = 'dbPassword'
      
    return loginData  
===============================================================================

Note: there are no hard and fast rules about what can go into the
connections.conf file.  If your authenticator needs more information
stored in connections.conf, it is fine to do so.

However, to avoid namespace collisions, you should probably prefix
any custom connection.conf entries with common prefix. For example,
if you are writing an NIS adapter, you should prefix entries in
connections.conf like:

  auth_nis_domain = MYDOMAIN

instead of something like:

  domain = MYDOMAIN

More examples:

  auth_ldap_*
  auth_pg_*
  auth_mysql_*
  auth_kerberos_*
  auth_custom_*


If you have written a customized login handler using backends not currently
in our samples file (i.e., against NIS, Kerberos, LDAP, etc) and would like
to donate your example for others to learn from, please email it to:

       info@gnue.org

