# LEG25062006
# LEG20080704
#   Martin Schwarzbauer added wikipedia_mysql authentication
#
# Several authentication modules which try to log in the use
# into some remote system like pop, ftp, etc.
#
# typically you would use it somehow like:
'''
   from RemoteAuth import <auth>, <map>, cookie
   auth = [<auth>, <map> <cookie>]
   auth_<*>_verbose=True
   auth_<auth>_server=<server where to authenticate>
   auth_<auth>_ssl=<True|False>
   # Create User Profile on first login
   # Note: need to login a second time then
   user_autocreate=True
'''
#
# the 'cookie' module is a remake of 'moin_cookie', but it does not
# use the username from the login form, instead it expects it to be
# found in the user_obj
#
# the <map> modules, currently 'gecos', map a name from the login
# form, to something else.  Often the <auth> module needs an account
# name or email address, which is not suitable as a WikiName.  The
# <map> modules can help out with this issue.
# map modules:
#   gecos .. gets the gecos field from the /etc/passwd file (or from
#            nss) extracts the name field and sticks the first two
#            strings there together
#
# the <auth> modules use some commonly used protocol which requires
# authentication.  The take the given username and password from the
# login form and try to authenticate with the respective protocol.
# Then they log out immediately from the service.  If the login was
# successful, the user gets authenticated to the Wiki.  So it suffices
# to have e.g. a POP server with the accounts of the users to get a
# kind of Single Signon for your Wiki.
# auth modules:
#   imap .. logs in to an IMAP server.
#   ftp  .. not tested
#
#
# Security note: it is surely prefereable to have the "remote" service
#   run on the same machine as the Wiki, and better even to run it on
#  "localhost".  This would reduce the chance of credencial leaking.
#
# Note: an interesting module would be the 'uri' auth module, it would
#   take an uri in the following form as username:
#
#   proto://user@host  e.g.:
#
#   imaps://fran=FrancisBacon@mail.hiho.com
#
#   the 'uri' module would then use the 'imap' module, set the server
#   to 'mail.hiho.com', the username to 'fran', and auth_imap_ssl to
#   True.  The Wiki username would be set to "FrancisBacon"
#
#   The uri module thus would allow a user to log in with credencials
#   from wherever they please.
#
#   Again note, that the user is not assured that this credencials
#   could eventually be leaked by flaws in the Wiki or in the
#   operating system where it is run.
# History:
#
#   LEG25062006: initial version
#
# TODO:
#   more modules ...
#
# Bugs:
#
#   When logging in (imap), the wiki says "unknown user", however the
#   user gets authenticated.


def gecos(request, **kw):
    """ not for authentication, but rather for determining
        the user name via a Gecos field entry from the
        /etc/passwd file on unix-systems.
        must be switched in between auth and cookie
    """
    u = kw.get('user_obj')

    # If there is no user object we are at the wrong place here
    if not u:
        return u, True

    import sys, traceback, os
    from MoinMoin import user

    verbose = request.cfg.auth_gecos_verbose
    
    # Extract the Gecos field from the nss passwd database
    # TODO: access /etc/passwd directly if nss not available
    try:
        P = os.popen("getent passwd "+u.auth_username,'r')
        passwd = P.readline()
        P.close
        # TODO: fails if the user does not exist
        gecos = passwd.split(':')[4]
        cn = gecos.split(',')[0]
        names = cn.split(' ')
        # TODO: verfiy that names is not empty, etc.
        u.name = names[0]+names[1]

    except:
        info = sys.exc_info()
        request.log("GECOS: caught an exception, traceback follows...")
        request.log(''.join(traceback.format_exception(*info)))
        u.name = u.auth_username

    if verbose:
        request.log("GECOS: mapped auth_name %s to name %s" % (u.auth_username, u.name))
        
    u.auth_method='gecos'
    u.auth_attribs=('name', 'auth_username', 'password')
    u.create_or_update(True)
    return u, True # cookie has to set the cookie to maintain the session alive
    

def cookie(request, **kw):
    """ authenticate via the MOIN_ID cookie
    this module must be chained in after another authentication
    module, it does not login, otherwise.
    In contrast to moin_cookie it does not look up the user locally.
    """
    
    username = kw.get('name')
    password = kw.get('password')
    login = kw.get('login')
    logout = kw.get('logout')
    u = kw.get('user_obj')
    
    from MoinMoin import auth, user
    import Cookie
    
    if login:
        if not u:
            return None, True

        if u.valid:
            auth.setCookie(request, u)
            return u, True # we make continuing possible, e.g. for smbmount
        
    try:
        cookie = Cookie.SimpleCookie(request.saved_cookie)
    except Cookie.CookieError:
        # ignore invalid cookies, else user can't relogin
        cookie = None
        
    if cookie and cookie.has_key('MOIN_ID'):
        u = user.User(request, id=cookie['MOIN_ID'].value,
                      auth_method='cookie', auth_attribs=())
        
        if logout:
            u.valid = 0 # just make user invalid, but remember him
            
            if u.valid:
                auth.setCookie(request, u) # refreshes cookie lifetime
                return u, True # use True to get other methods called, too
            
            else: # logout or invalid user
                auth.deleteCookie(request)
                return u, True # we return a invalidated user object, so that
            # following auth methods can get the name of
            # the user who logged out
    return u, True
                
                
def imap(request, **kw):
    """ get authentication data from form, authenticate via imap
        user profile must be handled e.g. by cookie
    """
    username = kw.get('name')
    password = kw.get('password')
    login = kw.get('login')
    user_obj = kw.get('user_obj')

    cfg = request.cfg
    verbose = cfg.auth_imap_verbose
    
    if verbose:
        request.log("IMAP: got name=%s login=%r" % (username, login))
    
    # we just intercept login, other requests have to be
    # handled by another auth handler
    if not login:
        return user_obj, True
    
    import sys, re, imaplib
    import traceback
    from MoinMoin import user

    u = None

    try:
        if cfg.auth_imap_ssl:
            M = imaplib.IMAP4_SSL(cfg.auth_imap_server)
        else:
            M = imaplib.IMAP4(cfg.auth_imap_server)
        try:
            M.login(username, password)
            u = user.User(request,
                          auth_username=username,
                          name=username,
                          password=password,
                          auth_method='imap',
                          auth_attribs=('name', 'auth_username', 'password',))
        except M.error, err:
            request.log("IMAP: Login failed: %s" % err)
            
    except:
        info = sys.exc_info()
        request.log("IMAP: caught an exception, traceback follows...")
        request.log(''.join(traceback.format_exception(*info)))


    if u:
        u.create_or_update(True)
    return u, True

def  wikipedia_mysql(request, **kw):
    """ get authentication data from Wikipedia database
    """
    username = kw.get('name')
    password = kw.get('password')
    login = kw.get('login')
    user_obj = kw.get('user_obj')

    cfg = request.cfg
    mysql_host = cfg.auth_wikipedia_mysql_host
    mysql_user = cfg.auth_wikipedia_mysql_user
    mysql_passwd = cfg.auth_wikipedia_mysql_passwd
    mysql_db = cfg.auth_wikipedia_mysql_db
   
    # we just intercept login, other requests have to be
    # handled by another auth handler
    if not login:
        return user_obj, True
    
    import sys, re, MySQLdb
    import traceback
    from MoinMoin import user

    u = None
        
    try:
        conn = MySQLdb.connect(host = mysql_host, user = mysql_user, passwd = mysql_passwd, db = mysql_db)
        cursor = conn.cursor()        
        query = """
        SELECT (user_password = MD5(CONCAT(user_id,'-',MD5('"""+password+"""')))) 
        FROM (
            SELECT user_id, user_password 
            FROM user
            WHERE user_name = '"""+username.capitalize()+"""'
            ) AS Temp;"""
       
        cursor.execute(query)
        
        result = cursor.fetchall() 

        # check if password and username are valid
        if len (result) == 1:
            if (result[0])[0]:
                
                # request.log("wikipedia_mysql: Login successful")
                # request.log("wikipedia_mysql Username:" + username)
                u = user.User(request,
                              auth_username=username,
                              name=username,
                              password=password,
                              auth_method='wikipedia_mysql',
                              auth_attribs=('name', 'auth_username', 'password',))
                u.valid = True

    except:
        info = sys.exc_info()
        request.log("Wikipedia: caught an exception, traceback follows...")
        request.log(''.join(traceback.format_exception(*info)))

    if u:
        u.create_or_update(True)

    # clean up
    cursor.close()
    conn.close()
        
    return u, True
    	
def ftp(request, **kw):
    """ get authentication data from form, authenticate via ftp
        user profile must be handled e.g. by cookie
    """
    username = kw.get('name')
    password = kw.get('password')
    login = kw.get('login')
    user_obj = kw.get('user_obj')

    cfg = request.cfg
    verbose = cfg.auth_ftp_verbose
    
    if verbose:
        request.log("FTP: got name=%s login=%r" % (username, login))
    
    # we just intercept login, other requests have to be
    # handled by another auth handler
    if not login:
        return user_obj, True
    
    import sys, re, ftplib
    import traceback
    from MoinMoin import user

    u = None

    try:
        F = ftplib.FTP(cfg.auth_ftp_server, username, password)
        u = user.User(request,
                      auth_username=username,
                      name=username,
                      password=password,
                      auth_method='ftp',
                      auth_attribs=('name', 'auth_username', 'password',))
    except:
        info = sys.exc_info()
        request.log("FTP: caught an exception, traceback follows...")
        request.log(''.join(traceback.format_exception(*info)))

    if u:
        u.create_or_update(True)
    return u, True
