Attachment 'auth.orig.py'

Download

   1 # -*- coding: iso-8859-1 -*-
   2 """
   3     MoinMoin - modular authentication code
   4 
   5     Here are some methods moin can use in cfg.auth authentication method list.
   6     The methods from that list get called (from request.py) in that sequence.
   7     They get request as first argument and also some more kw arguments:
   8        name: the value we did get from a POST of the UserPreferences page
   9              in the "name" form field (or None)
  10        password: the value of the password form field (or None)
  11        login: True if user has clicked on Login button
  12        logout: True if user has clicked on Logout button
  13        user_obj: the user_obj we have until now (user_obj returned from
  14                  previous auth method or None for first auth method)
  15        (we maybe add some more here)
  16 
  17     Use code like this to get them:
  18         name = kw.get('name') or ''
  19         password = kw.get('password') or ''
  20         login = kw.get('login')
  21         logout = kw.get('logout')
  22         request.log("got name=%s len(password)=%d login=%r logout=%r" % (name, len(password), login, logout))
  23     
  24     The called auth method then must return a tuple (user_obj, continue_flag).
  25     user_obj can be one of:
  26     * a (newly created) User object
  27     * None if we want to inhibit log in from previous auth methods
  28     * what we got as kw argument user_obj (meaning: no change).
  29     continue_flag is a boolean indication whether the auth loop shall continue
  30     trying other auth methods (or not).
  31 
  32     The methods give a kw arg "auth_attribs" to User.__init__ that tells
  33     which user attribute names are DETERMINED and set by this auth method and
  34     must not get changed by the user using the UserPreferences form.
  35     It also gives a kw arg "auth_method" that tells the name of the auth
  36     method that authentified the user.
  37     
  38     @copyright: 2005-2006 Bastian Blank, Florian Festi, Thomas Waldmann
  39     @copyright: 2005-2006 MoinMoin:AlexanderSchremmer
  40     @license: GNU GPL, see COPYING for details.
  41 """
  42 
  43 import time, Cookie
  44 from MoinMoin import user
  45 
  46 def log(request, **kw):
  47     """ just log the call, do nothing else """
  48     username = kw.get('name')
  49     password = kw.get('password')
  50     login = kw.get('login')
  51     logout = kw.get('logout')
  52     user_obj = kw.get('user_obj')
  53     request.log("auth.log: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
  54     return user_obj, True
  55 
  56 # some cookie functions used by moin_cookie auth
  57 def makeCookie(request, moin_id, maxage, expires):
  58     """ calculate a MOIN_ID cookie """
  59     c = Cookie.SimpleCookie()
  60     cfg = request.cfg
  61     c['MOIN_ID'] = moin_id
  62     c['MOIN_ID']['max-age'] = maxage
  63     if cfg.cookie_domain:
  64         c['MOIN_ID']['domain'] = cfg.cookie_domain
  65     if cfg.cookie_path:
  66         c['MOIN_ID']['path'] = cfg.cookie_path
  67     else:
  68         path = request.getScriptname()
  69         if not path:
  70             path = '/'
  71         c['MOIN_ID']['path'] = path
  72     # Set expires for older clients
  73     c['MOIN_ID']['expires'] = request.httpDate(when=expires, rfc='850')        
  74     return c.output()
  75 
  76 def setCookie(request, u):
  77     """ Set cookie for the user obj u
  78     
  79     cfg.cookie_lifetime and the user 'remember_me' setting set the
  80     lifetime of the cookie. lifetime in int hours, see table:
  81     
  82     value   cookie lifetime
  83     ----------------------------------------------------------------
  84      = 0    forever, ignoring user 'remember_me' setting
  85      > 0    n hours, or forever if user checked 'remember_me'
  86      < 0    -n hours, ignoring user 'remember_me' setting
  87     """
  88     # Calculate cookie maxage and expires
  89     lifetime = int(request.cfg.cookie_lifetime) * 3600 
  90     forever = 10*365*24*3600 # 10 years
  91     now = time.time()
  92     if not lifetime:
  93         maxage = forever
  94     elif lifetime > 0:
  95         if u.remember_me:
  96             maxage = forever
  97         else:
  98             maxage = lifetime
  99     elif lifetime < 0:
 100         maxage = (-lifetime)
 101     expires = now + maxage
 102     
 103     cookie = makeCookie(request, u.id, maxage, expires)
 104     # Set cookie
 105     request.setHttpHeader(cookie)
 106     # IMPORTANT: Prevent caching of current page and cookie
 107     request.disableHttpCaching()
 108 
 109 def deleteCookie(request):
 110     """ Delete the user cookie by sending expired cookie with null value
 111 
 112     According to http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.2
 113     Deleted cookie should have Max-Age=0. We also have expires
 114     attribute, which is probably needed for older browsers.
 115 
 116     Finally, delete the saved cookie and create a new user based on the new settings.
 117     """
 118     moin_id = ''
 119     maxage = 0
 120     # Set expires to one year ago for older clients
 121     expires = time.time() - (3600 * 24 * 365) # 1 year ago
 122     cookie = makeCookie(request, moin_id, maxage, expires) 
 123     # Set cookie
 124     request.setHttpHeader(cookie)
 125     # IMPORTANT: Prevent caching of current page and cookie        
 126     request.disableHttpCaching()
 127 
 128 def moin_cookie(request, **kw):
 129     """ authenticate via the MOIN_ID cookie """
 130     username = kw.get('name')
 131     password = kw.get('password')
 132     login = kw.get('login')
 133     logout = kw.get('logout')
 134     user_obj = kw.get('user_obj')
 135     #request.log("auth.moin_cookie: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
 136     if login:
 137         u = user.User(request, name=username, password=password,
 138                       auth_method='login_userpassword')
 139         if u.valid:
 140             setCookie(request, u)
 141             return u, True # we make continuing possible, e.g. for smbmount
 142         return user_obj, True
 143 
 144     try:
 145         cookie = Cookie.SimpleCookie(request.saved_cookie)
 146     except Cookie.CookieError:
 147         # ignore invalid cookies, else user can't relogin
 148         cookie = None
 149     if cookie and cookie.has_key('MOIN_ID'):
 150         u = user.User(request, id=cookie['MOIN_ID'].value,
 151                       auth_method='moin_cookie', auth_attribs=())
 152 
 153         if logout:
 154             u.valid = 0 # just make user invalid, but remember him
 155 
 156         if u.valid:
 157             setCookie(request, u) # refreshes cookie lifetime
 158             return u, True # use True to get other methods called, too
 159         else: # logout or invalid user
 160             deleteCookie(request)
 161             return u, True # we return a invalidated user object, so that
 162                            # following auth methods can get the name of
 163                            # the user who logged out
 164     return user_obj, True
 165 
 166 
 167 def http(request, **kw):
 168     """ authenticate via http basic/digest/ntlm auth """
 169     from MoinMoin.request import RequestTwisted, RequestCLI
 170     user_obj = kw.get('user_obj')
 171     u = None
 172     # check if we are running Twisted
 173     if isinstance(request, RequestTwisted):
 174         username = request.twistd.getUser()
 175         password = request.twistd.getPassword()
 176         # when using Twisted http auth, we use username and password from
 177         # the moin user profile, so both can be changed by user.
 178         u = user.User(request, auth_username=username, password=password,
 179                       auth_method='http', auth_attribs=())
 180 
 181     elif not isinstance(request, RequestCLI):
 182         env = request.env
 183         auth_type = env.get('AUTH_TYPE','')
 184         if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]:
 185             username = env.get('REMOTE_USER','')
 186             if auth_type in ('NTLM', 'Negotiate',):
 187                 # converting to standard case so the user can even enter wrong case
 188                 # (added since windows does not distinguish between e.g.
 189                 #  "Mike" and "mike")
 190                 username = username.split('\\')[-1] # split off domain e.g.
 191                                                     # from DOMAIN\user
 192                 # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
 193                 # put a comment sign in front of next line if you don't want that:
 194                 username = username.title()
 195             # when using http auth, we have external user name and password,
 196             # we don't use the moin user profile for those attributes.
 197             u = user.User(request, auth_username=username,
 198                           auth_method='http', auth_attribs=('name', 'password'))
 199 
 200     if u:
 201         u.create_or_update()
 202     if u and u.valid:
 203         return u, True # True to get other methods called, too
 204     else:
 205         return user_obj, True
 206 
 207 def sslclientcert(request, **kw):
 208     """ authenticate via SSL client certificate """
 209     from MoinMoin.request import RequestTwisted
 210     user_obj = kw.get('user_obj')
 211     u = None
 212     changed = False
 213     # check if we are running Twisted
 214     if isinstance(request, RequestTwisted):
 215         return user_obj, True # not supported if we run twisted
 216         # Addendum: this seems to need quite some twisted insight and coding.
 217         # A pointer i got on #twisted: divmod's vertex.sslverify
 218         # If you really need this, feel free to implement and test it and
 219         # submit a patch if it works.
 220     else:
 221         env = request.env
 222         if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS':
 223             # if we only want to accept some specific CA, do a check like:
 224             # if env.get('SSL_CLIENT_I_DN_OU') == "http://www.cacert.org"
 225             email = env.get('SSL_CLIENT_S_DN_Email', '')
 226             email_lower = email.lower()
 227             commonname = env.get('SSL_CLIENT_S_DN_CN', '')
 228             commonname_lower = commonname.lower()
 229             if email_lower or commonname_lower:
 230                 for uid in user.getUserList():
 231                     u = user.User(request, uid,
 232                                   auth_method='sslclientcert', auth_attribs=())
 233                     if email_lower and u.email.lower() == email_lower:
 234                         u.auth_attribs = ('email', 'password')
 235                         #this is only useful if same name should be used, as
 236                         #commonname is likely no CamelCase WikiName
 237                         #if commonname_lower != u.name.lower():
 238                         #    u.name = commonname
 239                         #    changed = True
 240                         #u.auth_attribs = ('email', 'name', 'password')
 241                         break
 242                     if commonname_lower and u.name.lower() == commonname_lower:
 243                         u.auth_attribs = ('name', 'password')
 244                         #this is only useful if same email should be used as
 245                         #specified in certificate.
 246                         #if email_lower != u.email.lower():
 247                         #    u.email = email
 248                         #    changed = True
 249                         #u.auth_attribs = ('name', 'email', 'password')
 250                         break
 251                 else:
 252                     u = None
 253 
 254     if u:
 255         u.create_or_update(changed)
 256     if u and u.valid:
 257         return u, True
 258     else:
 259         return user_obj, True
 260 
 261 
 262 def smb_mount(request, **kw):
 263     """ (u)mount a SMB server's share for username (using username/password for
 264         authentication at the SMB server). This can be used if you need access
 265         to files on some share via the wiki, but needs more code to be useful.
 266         If you don't need it, don't use it.
 267     """
 268     username = kw.get('name')
 269     password = kw.get('password')
 270     login = kw.get('login')
 271     logout = kw.get('logout')
 272     user_obj = kw.get('user_obj')
 273     cfg = request.cfg
 274     verbose = cfg.smb_verbose
 275     if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
 276     
 277     # we just intercept login to mount and logout to umount the smb share
 278     if login or logout:
 279         import os, pwd, subprocess
 280         web_username = cfg.smb_dir_user
 281         web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
 282         if logout and user_obj: # logout -> we don't have username in form
 283             username = user_obj.name # so we take it from previous auth method (moin_cookie e.g.)
 284         mountpoint = cfg.smb_mountpoint % {
 285             'username': username,
 286         }
 287         if login:
 288             cmd = u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode=%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1"
 289         elif logout:
 290             cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1"
 291             
 292         cmd = cmd % {
 293             'user': username,
 294             'uid': web_uid,
 295             'domain': cfg.smb_domain,
 296             'server': cfg.smb_server,
 297             'share': cfg.smb_share,
 298             'mountpoint': mountpoint,
 299             'dir_mode': cfg.smb_dir_mode,
 300             'file_mode': cfg.smb_file_mode,
 301             'iocharset': cfg.smb_iocharset,
 302             'log': cfg.smb_log,
 303         }
 304         env = os.environ.copy()
 305         if login:
 306             try:
 307                 os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
 308             except OSError, err:
 309                 pass
 310             env['PASSWD'] = password.encode(cfg.smb_coding)
 311         subprocess.call(cmd.encode(cfg.smb_coding), env=env, shell=True)
 312     return user_obj, True
 313 
 314 
 315 def ldap_login(request, **kw):
 316     """ get authentication data from form, authenticate against LDAP (or Active Directory),
 317         fetch some user infos from LDAP and create a user profile for that user that must
 318         be used by subsequent auth plugins (like moin_cookie) as we never return a user
 319         object from ldap_login.
 320     """
 321     username = kw.get('name')
 322     password = kw.get('password')
 323     login = kw.get('login')
 324     logout = kw.get('logout')
 325     user_obj = kw.get('user_obj')
 326 
 327     cfg = request.cfg
 328     verbose = cfg.ldap_verbose
 329     
 330     if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
 331     
 332     # we just intercept login and logout for ldap, other requests have to be
 333     # handled by another auth handler
 334     if not login and not logout:
 335         return user_obj, True
 336     
 337     import sys, re
 338     import ldap
 339     import traceback
 340 
 341     u = None
 342     coding = cfg.ldap_coding
 343     try:
 344         if verbose: request.log("LDAP: Trying to initialize %s." % cfg.ldap_uri)
 345         l = ldap.initialize(cfg.ldap_uri)
 346         if verbose: request.log("LDAP: Connected to LDAP server %s." % cfg.ldap_uri)
 347         # you can use %(username)s and %(password)s here to get the stuff entered in the form:
 348         ldap_binddn = cfg.ldap_binddn % locals()
 349         ldap_bindpw = cfg.ldap_bindpw % locals()
 350         l.simple_bind_s(ldap_binddn.encode(coding), ldap_bindpw.encode(coding))
 351         if verbose: request.log("LDAP: Bound with binddn %s" % ldap_binddn)
 352 
 353         filterstr = "(%s=%s)" % (cfg.ldap_name_attribute, username)
 354         if verbose: request.log("LDAP: Searching %s" % filterstr)
 355         lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope,
 356                              filterstr.encode(coding), timeout=cfg.ldap_timeout)
 357         result_length = len(lusers)
 358         if result_length != 1:
 359             if result_length > 1:
 360                 request.log("LDAP: Search found more than one (%d) matches for %s." % (len(lusers), filterstr))
 361             if result_length == 0:
 362                 if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, ))
 363             return user_obj, True
 364 
 365         dn, ldap_dict = lusers[0]
 366         if verbose:
 367             request.log("LDAP: debug lusers = %r" % lusers)
 368             for key,val in ldap_dict.items():
 369                 request.log("LDAP: %s: %s" % (key, val))
 370 
 371         try:
 372             if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn)
 373             l.simple_bind_s(dn, password.encode(coding))
 374             if verbose: request.log("LDAP: Bound with dn %s (username: %s)" % (dn, username))
 375             
 376             email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0]
 377             email = email.decode(coding)
 378             sn, gn = ldap_dict.get('sn', [''])[0], ldap_dict.get('givenName', [''])[0]
 379             aliasname = ''
 380             if sn and gn:
 381                 aliasname = "%s, %s" % (sn, gn)
 382             elif sn:
 383                 aliasname = sn
 384             aliasname = aliasname.decode(coding)
 385             
 386             u = user.User(request, auth_username=username, password=password, auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',))
 387             u.name = username
 388             u.aliasname = aliasname
 389             u.email = email
 390             u.remember_me = 0 # 0 enforces cookie_lifetime config param
 391             if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname))
 392             
 393         except ldap.INVALID_CREDENTIALS, err:
 394             request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username))
 395 
 396     except:
 397         info = sys.exc_info()
 398         request.log("LDAP: caught an exception, traceback follows...")
 399         request.log(''.join(traceback.format_exception(*info)))
 400 
 401     if u:
 402         u.create_or_update(True)
 403     return user_obj, True # moin_cookie has to set the cookie and return the user obj
 404 
 405 
 406 def interwiki(request, **kw):
 407     # TODO use auth_method and auth_attribs for User object
 408     username = kw.get('name')
 409     password = kw.get('password')
 410     login = kw.get('login')
 411     logout = kw.get('logout')
 412     user_obj = kw.get('user_obj')
 413 
 414     if login:
 415         wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
 416 
 417         if err or wikitag not in request.cfg.trusted_wikis:
 418             return user_obj, True
 419         
 420         if password:
 421             import xmlrpclib
 422             homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2")
 423             account_data = homewiki.getUser(wikitail, password)
 424             if isinstance(account_data, str):
 425                 # show error message
 426                 return user_obj, True
 427             
 428             u = user.User(request, name=username)
 429             for key, value in account_data.iteritems():
 430                 if key not in ["may", "id", "valid", "trusted"
 431                                "auth_username",
 432                                "name", "aliasname",
 433                                "enc_passwd"]:
 434                     setattr(u, key, value)
 435             u.save()
 436             setCookie(request, u)
 437             return u, True
 438         else:
 439             pass
 440             # XXX redirect to homewiki
 441     
 442     return user_obj, True
 443 
 444 
 445 class php_session:
 446     """ Authentication module for PHP based frameworks
 447         Authenticates via PHP session cookie. Currently supported systems:
 448 
 449         * eGroupware 1.2 ("egw")
 450          * You need to configure eGroupware in the "header setup" to use
 451            "php sessions plus restore"
 452 
 453         @copyright: 2005 by MoinMoin:AlexanderSchremmer
 454             - Thanks to Spreadshirt
 455     """
 456 
 457     def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"):
 458         """ @param apps: A list of the enabled applications. See above for
 459             possible keys.
 460             @param s_path: The path where the PHP sessions are stored.
 461             @param s_prefix: The prefix of the session files.
 462         """
 463         
 464         self.s_path = s_path
 465         self.s_prefix = s_prefix
 466         self.apps = apps
 467 
 468     def __call__(self, request, **kw):
 469         def handle_egroupware(session):
 470             """ Extracts name, fullname and email from the session. """
 471             username = session['egw_session']['session_lid'].split("@", 1)[0]
 472             known_accounts = session['egw_info_cache']['accounts']['cache']['account_data']
 473             
 474             # if the next line breaks, then the cache was not filled with the current
 475             # user information
 476             user_info = [value for key, value in known_accounts.items()
 477                          if value['account_lid'] == username][0]
 478             name = user_info.get('fullname', '')
 479             email = user_info.get('email', '')
 480             
 481             dec = lambda x: x and x.decode("iso-8859-1")
 482             
 483             return dec(username), dec(email), dec(name)
 484         
 485         import Cookie, urllib
 486         from MoinMoin.user import User
 487         from MoinMoin.util import sessionParser
 488     
 489         user_obj = kw.get('user_obj')
 490         try:
 491             cookie = Cookie.SimpleCookie(request.saved_cookie)
 492         except Cookie.CookieError: # ignore invalid cookies
 493             cookie = None
 494         if cookie:
 495             for cookiename in cookie.keys():
 496                 cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1')
 497                 session = sessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix)
 498                 if session:
 499                     if "egw" in self.apps and session.get('egw_session', None):
 500                         username, email, name = handle_egroupware(session)
 501                         break
 502             else:
 503                 return user_obj, True
 504             
 505             user = User(request, name=username, auth_username=username)
 506             
 507             changed = False
 508             if name != user.aliasname:
 509                 user.aliasname = name
 510                 changed = True
 511             if email != user.email:
 512                 user.email = email
 513                 changed = True
 514             
 515             if user:
 516                 user.create_or_update(changed)
 517             if user and user.valid:
 518                 return user, True # True to get other methods called, too
 519         return user_obj, True # continue with next method in auth list

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2006-12-06 01:07:52, 21.0 KB) [[attachment:auth.orig.py]]
  • [get | view] (2006-12-06 01:08:07, 21.6 KB) [[attachment:auth.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.