Mercurial > moin > 1.9
changeset 776:ab9cd47eb066
teared auth code into single files, basic built-in moin_login method and also session handling code is still in auth/__init__.py
author | Thomas Waldmann <tw AT waldmann-edv DOT de> |
---|---|
date | Sat, 10 Jun 2006 10:12:59 +0200 |
parents | 0e3327b36bc5 |
children | e80f12c41f77 |
files | MoinMoin/auth/__init__.py MoinMoin/auth/http.py MoinMoin/auth/interwiki.py MoinMoin/auth/ldap_login.py MoinMoin/auth/log.py MoinMoin/auth/mysql_group.py MoinMoin/auth/php_session.py MoinMoin/auth/smb_mount.py MoinMoin/auth/sslclientcert.py docs/CHANGES |
diffstat | 10 files changed, 513 insertions(+), 425 deletions(-) [+] |
line wrap: on
line diff
--- a/MoinMoin/auth/__init__.py Sat Jun 10 00:24:50 2006 +0200 +++ b/MoinMoin/auth/__init__.py Sat Jun 10 10:12:59 2006 +0200 @@ -59,20 +59,10 @@ return ''.join([random.choice(safe) for i in range(random_length)]) def make_security_hash(request, data, securitystring=''): - """ generate a hash string based on site configuration's cfg.cookie_security, + """ generate a hash string based on site configuration's cfg.cookie_secret, securitystring and the data. """ - return hmac.new(request.cfg.cookie_security + securitystring, data).hexdigest() - -def log(request, **kw): - """ just log the call, do nothing else """ - username = kw.get('name') - password = kw.get('password') - login = kw.get('login') - logout = kw.get('logout') - user_obj = kw.get('user_obj') - request.log("auth.log: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj)) - return user_obj, True + return hmac.new(request.cfg.cookie_secret + securitystring, data).hexdigest() def makeCookie(request, cookie_name, cookie_string, maxage, expires): """ create an appropriate cookie """ @@ -272,415 +262,3 @@ setSessionCookie(request, u) # refreshes cookie lifetime return u, True # use True to get other methods called, too -def http(request, **kw): - """ authenticate via http basic/digest/ntlm auth """ - from MoinMoin.request import TWISTED, CLI - user_obj = kw.get('user_obj') - u = None - # check if we are running Twisted - if isinstance(request, TWISTED.Request): - username = request.twistd.getUser() - password = request.twistd.getPassword() - # when using Twisted http auth, we use username and password from - # the moin user profile, so both can be changed by user. - u = user.User(request, auth_username=username, password=password, - auth_method='http', auth_attribs=()) - - elif not isinstance(request, CLI.Request): - env = request.env - auth_type = env.get('AUTH_TYPE','') - if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]: - username = env.get('REMOTE_USER','') - if auth_type in ('NTLM', 'Negotiate',): - # converting to standard case so the user can even enter wrong case - # (added since windows does not distinguish between e.g. - # "Mike" and "mike") - username = username.split('\\')[-1] # split off domain e.g. - # from DOMAIN\user - # this "normalizes" the login name from {meier, Meier, MEIER} to Meier - # put a comment sign in front of next line if you don't want that: - username = username.title() - # when using http auth, we have external user name and password, - # we don't use the moin user profile for those attributes. - u = user.User(request, auth_username=username, - auth_method='http', auth_attribs=('name', 'password')) - - if u: - u.create_or_update() - if u and u.valid: - return u, True # True to get other methods called, too - else: - return user_obj, True - -def sslclientcert(request, **kw): - """ authenticate via SSL client certificate """ - from MoinMoin.request import TWISTED - user_obj = kw.get('user_obj') - u = None - changed = False - # check if we are running Twisted - if isinstance(request, TWISTED.Request): - return user_obj, True # not supported if we run twisted - # Addendum: this seems to need quite some twisted insight and coding. - # A pointer i got on #twisted: divmod's vertex.sslverify - # If you really need this, feel free to implement and test it and - # submit a patch if it works. - else: - env = request.env - if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS': - # if we only want to accept some specific CA, do a check like: - # if env.get('SSL_CLIENT_I_DN_OU') == "http://www.cacert.org" - email = env.get('SSL_CLIENT_S_DN_Email', '') - email_lower = email.lower() - commonname = env.get('SSL_CLIENT_S_DN_CN', '') - commonname_lower = commonname.lower() - if email_lower or commonname_lower: - for uid in user.getUserList(request): - u = user.User(request, uid, - auth_method='sslclientcert', auth_attribs=()) - if email_lower and u.email.lower() == email_lower: - u.auth_attribs = ('email', 'password') - #this is only useful if same name should be used, as - #commonname is likely no CamelCase WikiName - #if commonname_lower != u.name.lower(): - # u.name = commonname - # changed = True - #u.auth_attribs = ('email', 'name', 'password') - break - if commonname_lower and u.name.lower() == commonname_lower: - u.auth_attribs = ('name', 'password') - #this is only useful if same email should be used as - #specified in certificate. - #if email_lower != u.email.lower(): - # u.email = email - # changed = True - #u.auth_attribs = ('name', 'email', 'password') - break - else: - u = None - if u is None: - # user wasn't found, so let's create a new user object - u = user.User(request, name=commonname_lower, auth_username=commonname_lower) - - if u: - u.create_or_update(changed) - if u and u.valid: - return u, True - else: - return user_obj, True - -def smb_mount(request, **kw): - """ (u)mount a SMB server's share for username (using username/password for - authentication at the SMB server). This can be used if you need access - to files on some share via the wiki, but needs more code to be useful. - If you don't need it, don't use it. - """ - username = kw.get('name') - password = kw.get('password') - login = kw.get('login') - logout = kw.get('logout') - user_obj = kw.get('user_obj') - cfg = request.cfg - verbose = cfg.smb_verbose - if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout)) - - # we just intercept login to mount and logout to umount the smb share - if login or logout: - import os, pwd, subprocess - web_username = cfg.smb_dir_user - web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid? - if logout and user_obj: # logout -> we don't have username in form - username = user_obj.name # so we take it from previous auth method (moin_cookie e.g.) - mountpoint = cfg.smb_mountpoint % { - 'username': username, - } - if login: - 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" - elif logout: - cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1" - - cmd = cmd % { - 'user': username, - 'uid': web_uid, - 'domain': cfg.smb_domain, - 'server': cfg.smb_server, - 'share': cfg.smb_share, - 'mountpoint': mountpoint, - 'dir_mode': cfg.smb_dir_mode, - 'file_mode': cfg.smb_file_mode, - 'iocharset': cfg.smb_iocharset, - 'log': cfg.smb_log, - } - env = os.environ.copy() - if login: - try: - os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us! - except OSError, err: - pass - env['PASSWD'] = password.encode(cfg.smb_coding) - subprocess.call(cmd.encode(cfg.smb_coding), env=env, shell=True) - return user_obj, True - -def ldap_login(request, **kw): - """ get authentication data from form, authenticate against LDAP (or Active Directory), - fetch some user infos from LDAP and create a user profile for that user that must - be used by subsequent auth plugins (like moin_cookie) as we never return a user - object from ldap_login. - """ - username = kw.get('name') - password = kw.get('password') - login = kw.get('login') - logout = kw.get('logout') - user_obj = kw.get('user_obj') - - cfg = request.cfg - verbose = cfg.ldap_verbose - - if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout)) - - # we just intercept login and logout for ldap, other requests have to be - # handled by another auth handler - if not login and not logout: - return user_obj, True - - import sys, re - import ldap - import traceback - - u = None - coding = cfg.ldap_coding - try: - if verbose: request.log("LDAP: Trying to initialize %s." % cfg.ldap_uri) - l = ldap.initialize(cfg.ldap_uri) - if verbose: request.log("LDAP: Connected to LDAP server %s." % cfg.ldap_uri) - # you can use %(username)s and %(password)s here to get the stuff entered in the form: - ldap_binddn = cfg.ldap_binddn % locals() - ldap_bindpw = cfg.ldap_bindpw % locals() - l.simple_bind_s(ldap_binddn.encode(coding), ldap_bindpw.encode(coding)) - if verbose: request.log("LDAP: Bound with binddn %s" % ldap_binddn) - - filterstr = "(%s=%s)" % (cfg.ldap_name_attribute, username) - if verbose: request.log("LDAP: Searching %s" % filterstr) - lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope, - filterstr.encode(coding), timeout=cfg.ldap_timeout) - result_length = len(lusers) - if result_length != 1: - if result_length > 1: - request.log("LDAP: Search found more than one (%d) matches for %s." % (len(lusers), filterstr)) - if result_length == 0: - if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, )) - return user_obj, True - - dn, ldap_dict = lusers[0] - if verbose: - request.log("LDAP: debug lusers = %r" % lusers) - for key,val in ldap_dict.items(): - request.log("LDAP: %s: %s" % (key, val)) - - try: - if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn) - l.simple_bind_s(dn, password.encode(coding)) - if verbose: request.log("LDAP: Bound with dn %s (username: %s)" % (dn, username)) - - email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0] - email = email.decode(coding) - sn, gn = ldap_dict.get('sn', [''])[0], ldap_dict.get('givenName', [''])[0] - aliasname = '' - if sn and gn: - aliasname = "%s, %s" % (sn, gn) - elif sn: - aliasname = sn - aliasname = aliasname.decode(coding) - - u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',)) - u.name = username - u.aliasname = aliasname - u.email = email - u.remember_me = 0 # 0 enforces cookie_lifetime config param - if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname)) - - except ldap.INVALID_CREDENTIALS, err: - request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username)) - - except: - info = sys.exc_info() - request.log("LDAP: caught an exception, traceback follows...") - request.log(''.join(traceback.format_exception(*info))) - - if u: - u.create_or_update(True) - return u, True # moin_cookie has to set the cookie and return the user obj - -def interwiki(request, **kw): - # TODO use auth_method and auth_attribs for User object - username = kw.get('name') - password = kw.get('password') - login = kw.get('login') - logout = kw.get('logout') - user_obj = kw.get('user_obj') - - if login: - wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username) - - if err or wikitag not in request.cfg.trusted_wikis: - return user_obj, True - - if password: - import xmlrpclib - homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2") - account_data = homewiki.getUser(wikitail, password) - if isinstance(account_data, str): - # show error message - return user_obj, True - - u = user.User(request, name=username) - for key, value in account_data.iteritems(): - if key not in ["may", "id", "valid", "trusted" - "auth_username", - "name", "aliasname", - "enc_passwd"]: - setattr(u, key, value) - u.save() - setSessionCookie(request, u) - return u, True - else: - pass - # XXX redirect to homewiki - - return user_obj, True - -class php_session: - """ Authentication module for PHP based frameworks - Authenticates via PHP session cookie. Currently supported systems: - - * eGroupware 1.2 ("egw") - * You need to configure eGroupware in the "header setup" to use - "php sessions plus restore" - - @copyright: 2005 by MoinMoin:AlexanderSchremmer - - Thanks to Spreadshirt - """ - def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"): - """ @param apps: A list of the enabled applications. See above for - possible keys. - @param s_path: The path where the PHP sessions are stored. - @param s_prefix: The prefix of the session files. - """ - - self.s_path = s_path - self.s_prefix = s_prefix - self.apps = apps - - def __call__(self, request, **kw): - def handle_egroupware(session): - """ Extracts name, fullname and email from the session. """ - username = session['egw_session']['session_lid'].split("@", 1)[0] - known_accounts = session['egw_info_cache']['accounts']['cache']['account_data'] - - # if the next line breaks, then the cache was not filled with the current - # user information - user_info = [value for key, value in known_accounts.items() - if value['account_lid'] == username][0] - name = user_info.get('fullname', '') - email = user_info.get('email', '') - - dec = lambda x: x and x.decode("iso-8859-1") - - return dec(username), dec(email), dec(name) - - import Cookie, urllib - from MoinMoin.user import User - from MoinMoin.auth import _PHPsessionParser - - user_obj = kw.get('user_obj') - try: - cookie = Cookie.SimpleCookie(request.saved_cookie) - except Cookie.CookieError: # ignore invalid cookies - cookie = None - if cookie: - for cookiename in cookie.keys(): - cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1') - session = _PHPsessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix) - if session: - if "egw" in self.apps and session.get('egw_session', None): - username, email, name = handle_egroupware(session) - break - else: - return user_obj, True - - user = User(request, name=username, auth_username=username) - - changed = False - if name != user.aliasname: - user.aliasname = name - changed = True - if email != user.email: - user.email = email - changed = True - - if user: - user.create_or_update(changed) - if user and user.valid: - return user, True # True to get other methods called, too - return user_obj, True # continue with next method in auth list - -def mysql_group(request, **kw): - """ Authorize via MySQL group DB. - - We require an already-authenticated user_obj. - We don't worry about the type of request (login, logout, neither). - We just check user is part of authorized group. - """ - import MySQLdb - - username = kw.get('name') -# login = kw.get('login') -# logout = kw.get('logout') - user_obj = kw.get('user_obj') - - cfg = request.cfg - verbose = False - - if hasattr(cfg, 'mysql_group_verbose'): - verbose = cfg.mysql_group_verbose - - if verbose: request.log("auth.mysql_group: name=%s user_obj=%r" % (username, user_obj)) - - # Has any other method successfully authenticated? - if user_obj is not None and user_obj.valid: - # Yes - we can do stuff! - if verbose: request.log("mysql_group got valid user from previous auth method, trying authz...") - if verbose: request.log("mysql_group got auth_username %s." % user_obj.auth_username) - - # XXX Check auth_username for dodgy chars (should be none as it is authenticated, but...) - - # OK, now check mysql! - try: - m = MySQLdb.connect(host=cfg.mysql_group_dbhost, - user=cfg.mysql_group_dbuser, - passwd=cfg.mysql_group_dbpass, - db=cfg.mysql_group_dbname, - ) - except: - import sys - import traceback - info = sys.exc_info() - request.log("mysql_group: authorization failed due to exception connecting to DB, traceback follows...") - request.log(''.join(traceback.format_exception(*info))) - return None, False - - c = m.cursor() - c.execute(cfg.mysql_group_query, user_obj.auth_username) - results = c.fetchall() - if results: - # Checked out OK - if verbose: request.log("mysql_group got %d results -- authorized!" % len(results)) - return user_obj, True # we make continuing possible, e.g. for smbmount - else: - if verbose: request.log("mysql_group did not get match from DB -- not authorized") - return None, False - else: - # No other method succeeded, so we cannot authorize -- must fail - if verbose: request.log("mysql_group did not get valid user from previous auth method, cannot authorize") - return None, False -
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/http.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,53 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - http authentication + + You need either your webserver configured for doing HTTP auth (like Apache + reading some .htpasswd file) or Twisted (will accept HTTP auth against + password stored in moin user profile, but currently will NOT ask for auth). + + @copyright: 2006 by MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" +from MoinMoin import user +from MoinMoin.request import TWISTED, CLI + +def http(request, **kw): + """ authenticate via http basic/digest/ntlm auth """ + user_obj = kw.get('user_obj') + u = None + # check if we are running Twisted + if isinstance(request, TWISTED.Request): + username = request.twistd.getUser() + password = request.twistd.getPassword() + # when using Twisted http auth, we use username and password from + # the moin user profile, so both can be changed by user. + u = user.User(request, auth_username=username, password=password, + auth_method='http', auth_attribs=()) + + elif not isinstance(request, CLI.Request): + env = request.env + auth_type = env.get('AUTH_TYPE','') + if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]: + username = env.get('REMOTE_USER','') + if auth_type in ('NTLM', 'Negotiate',): + # converting to standard case so the user can even enter wrong case + # (added since windows does not distinguish between e.g. + # "Mike" and "mike") + username = username.split('\\')[-1] # split off domain e.g. + # from DOMAIN\user + # this "normalizes" the login name from {meier, Meier, MEIER} to Meier + # put a comment sign in front of next line if you don't want that: + username = username.title() + # when using http auth, we have external user name and password, + # we don't use the moin user profile for those attributes. + u = user.User(request, auth_username=username, + auth_method='http', auth_attribs=('name', 'password')) + + if u: + u.create_or_update() + if u and u.valid: + return u, True # True to get other methods called, too + else: + return user_obj, True +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/interwiki.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,50 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - authentication using a remote wiki + + This is completely untested and rather has to be seen as an idea + than a working implementation. + + @copyright: 2005 by ??? + @license: GNU GPL, see COPYING for details. +""" +import xmlrpclib +from MoinMoin import auth, wikiutil, user + +def interwiki(request, **kw): + # TODO use auth_method and auth_attribs for User object + username = kw.get('name') + password = kw.get('password') + login = kw.get('login') + logout = kw.get('logout') + user_obj = kw.get('user_obj') + + if login: + wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username) + + if err or wikitag not in request.cfg.trusted_wikis: + return user_obj, True + + if password: + homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2") + account_data = homewiki.getUser(wikitail, password) + if isinstance(account_data, str): + # show error message + return user_obj, True + + u = user.User(request, name=username) + for key, value in account_data.iteritems(): + if key not in ["may", "id", "valid", "trusted" + "auth_username", + "name", "aliasname", + "enc_passwd"]: + setattr(u, key, value) + u.save() + auth.setSessionCookie(request, u) + return u, True + else: + pass + # XXX redirect to homewiki + + return user_obj, True +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/ldap_login.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,101 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - LDAP / Active Directory authentication + + This code only creates a user object, the session has to be established by + the auth.moin_session auth plugin. + + @copyright: 2006 by MoinMoin:ThomasWaldmann, Nick Phillips + @license: GNU GPL, see COPYING for details. +""" +import sys, re +import traceback +import ldap +from MoinMoin import user + +def ldap_login(request, **kw): + """ get authentication data from form, authenticate against LDAP (or Active Directory), + fetch some user infos from LDAP and create a user profile for that user that must + be used by subsequent auth plugins (like moin_cookie) as we never return a user + object from ldap_login. + """ + username = kw.get('name') + password = kw.get('password') + login = kw.get('login') + logout = kw.get('logout') + user_obj = kw.get('user_obj') + + cfg = request.cfg + verbose = cfg.ldap_verbose + + if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout)) + + # we just intercept login and logout for ldap, other requests have to be + # handled by another auth handler + if not login and not logout: + return user_obj, True + + u = None + coding = cfg.ldap_coding + try: + if verbose: request.log("LDAP: Trying to initialize %s." % cfg.ldap_uri) + l = ldap.initialize(cfg.ldap_uri) + if verbose: request.log("LDAP: Connected to LDAP server %s." % cfg.ldap_uri) + # you can use %(username)s and %(password)s here to get the stuff entered in the form: + ldap_binddn = cfg.ldap_binddn % locals() + ldap_bindpw = cfg.ldap_bindpw % locals() + l.simple_bind_s(ldap_binddn.encode(coding), ldap_bindpw.encode(coding)) + if verbose: request.log("LDAP: Bound with binddn %s" % ldap_binddn) + + filterstr = "(%s=%s)" % (cfg.ldap_name_attribute, username) + if verbose: request.log("LDAP: Searching %s" % filterstr) + lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope, + filterstr.encode(coding), timeout=cfg.ldap_timeout) + result_length = len(lusers) + if result_length != 1: + if result_length > 1: + request.log("LDAP: Search found more than one (%d) matches for %s." % (len(lusers), filterstr)) + if result_length == 0: + if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, )) + return user_obj, True + + dn, ldap_dict = lusers[0] + if verbose: + request.log("LDAP: debug lusers = %r" % lusers) + for key,val in ldap_dict.items(): + request.log("LDAP: %s: %s" % (key, val)) + + try: + if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn) + l.simple_bind_s(dn, password.encode(coding)) + if verbose: request.log("LDAP: Bound with dn %s (username: %s)" % (dn, username)) + + email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0] + email = email.decode(coding) + sn, gn = ldap_dict.get('sn', [''])[0], ldap_dict.get('givenName', [''])[0] + aliasname = '' + if sn and gn: + aliasname = "%s, %s" % (sn, gn) + elif sn: + aliasname = sn + aliasname = aliasname.decode(coding) + + u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',)) + u.name = username + u.aliasname = aliasname + u.email = email + u.remember_me = 0 # 0 enforces cookie_lifetime config param + if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname)) + + except ldap.INVALID_CREDENTIALS, err: + request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username)) + + except: + info = sys.exc_info() + request.log("LDAP: caught an exception, traceback follows...") + request.log(''.join(traceback.format_exception(*info))) + + if u: + u.create_or_update(True) + return u, True # moin_session has to set the cookie +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/log.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,21 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - logging auth plugin + + This does nothing except logging the auth parameters (the password is NOT + logged, of course). + + @copyright: 2006 by MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" + +def log(request, **kw): + """ just log the call, do nothing else """ + username = kw.get('name') + password = kw.get('password') + login = kw.get('login') + logout = kw.get('logout') + user_obj = kw.get('user_obj') + request.log("auth.log: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj)) + return user_obj, True +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/mysql_group.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,71 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - auth plugin doing a check against MySQL group db + + ... + + @copyright: 2006 by Nick Phillips + @license: GNU GPL, see COPYING for details. +""" + +import MySQLdb + +def mysql_group(request, **kw): + """ Authorize via MySQL group DB. + + We require an already-authenticated user_obj. + We don't worry about the type of request (login, logout, neither). + We just check user is part of authorized group. + """ + + username = kw.get('name') +# login = kw.get('login') +# logout = kw.get('logout') + user_obj = kw.get('user_obj') + + cfg = request.cfg + verbose = False + + if hasattr(cfg, 'mysql_group_verbose'): + verbose = cfg.mysql_group_verbose + + if verbose: request.log("auth.mysql_group: name=%s user_obj=%r" % (username, user_obj)) + + # Has any other method successfully authenticated? + if user_obj is not None and user_obj.valid: + # Yes - we can do stuff! + if verbose: request.log("mysql_group got valid user from previous auth method, trying authz...") + if verbose: request.log("mysql_group got auth_username %s." % user_obj.auth_username) + + # XXX Check auth_username for dodgy chars (should be none as it is authenticated, but...) + + # OK, now check mysql! + try: + m = MySQLdb.connect(host=cfg.mysql_group_dbhost, + user=cfg.mysql_group_dbuser, + passwd=cfg.mysql_group_dbpass, + db=cfg.mysql_group_dbname, + ) + except: + import sys + import traceback + info = sys.exc_info() + request.log("mysql_group: authorization failed due to exception connecting to DB, traceback follows...") + request.log(''.join(traceback.format_exception(*info))) + return None, False + + c = m.cursor() + c.execute(cfg.mysql_group_query, user_obj.auth_username) + results = c.fetchall() + if results: + # Checked out OK + if verbose: request.log("mysql_group got %d results -- authorized!" % len(results)) + return user_obj, True # we make continuing possible, e.g. for smbmount + else: + if verbose: request.log("mysql_group did not get match from DB -- not authorized") + return None, False + else: + # No other method succeeded, so we cannot authorize -- must fail + if verbose: request.log("mysql_group did not get valid user from previous auth method, cannot authorize") + return None, False +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/php_session.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,80 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - PHP session cookie authentication + + Currently supported systems: + + * eGroupware 1.2 ("egw") + * You need to configure eGroupware in the "header setup" to use + "php sessions plus restore" + + @copyright: 2005 by MoinMoin:AlexanderSchremmer (Thanks to Spreadshirt) + @license: GNU GPL, see COPYING for details. +""" + +import Cookie, urllib +from MoinMoin import user +from MoinMoin.auth import _PHPsessionParser + +class php_session: + """ PHP session cookie authentication """ + def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"): + """ @param apps: A list of the enabled applications. See above for + possible keys. + @param s_path: The path where the PHP sessions are stored. + @param s_prefix: The prefix of the session files. + """ + + self.s_path = s_path + self.s_prefix = s_prefix + self.apps = apps + + def __call__(self, request, **kw): + def handle_egroupware(session): + """ Extracts name, fullname and email from the session. """ + username = session['egw_session']['session_lid'].split("@", 1)[0] + known_accounts = session['egw_info_cache']['accounts']['cache']['account_data'] + + # if the next line breaks, then the cache was not filled with the current + # user information + user_info = [value for key, value in known_accounts.items() + if value['account_lid'] == username][0] + name = user_info.get('fullname', '') + email = user_info.get('email', '') + + dec = lambda x: x and x.decode("iso-8859-1") + + return dec(username), dec(email), dec(name) + + user_obj = kw.get('user_obj') + try: + cookie = Cookie.SimpleCookie(request.saved_cookie) + except Cookie.CookieError: # ignore invalid cookies + cookie = None + if cookie: + for cookiename in cookie.keys(): + cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1') + session = _PHPsessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix) + if session: + if "egw" in self.apps and session.get('egw_session', None): + username, email, name = handle_egroupware(session) + break + else: + return user_obj, True + + user = user.User(request, name=username, auth_username=username) + + changed = False + if name != user.aliasname: + user.aliasname = name + changed = True + if email != user.email: + user.email = email + changed = True + + if user: + user.create_or_update(changed) + if user and user.valid: + return user, True # True to get other methods called, too + return user_obj, True # continue with next method in auth list +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/smb_mount.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,61 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - auth plugin for (un)mounting a smb share + + (u)mount a SMB server's share for username (using username/password for + authentication at the SMB server). This can be used if you need access + to files on some share via the wiki, but needs more code to be useful. + + @copyright: 2006 by MoinMoin:ThomasWaldmann + @license: GNU GPL, see COPYING for details. +""" + + +def smb_mount(request, **kw): + """ auth plugin for (un)mounting an smb share """ + username = kw.get('name') + password = kw.get('password') + login = kw.get('login') + logout = kw.get('logout') + user_obj = kw.get('user_obj') + cfg = request.cfg + verbose = cfg.smb_verbose + if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout)) + + # we just intercept login to mount and logout to umount the smb share + if login or logout: + import os, pwd, subprocess + web_username = cfg.smb_dir_user + web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid? + if logout and user_obj: # logout -> we don't have username in form + username = user_obj.name # so we take it from previous auth method (moin_cookie e.g.) + mountpoint = cfg.smb_mountpoint % { + 'username': username, + } + if login: + 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" + elif logout: + cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1" + + cmd = cmd % { + 'user': username, + 'uid': web_uid, + 'domain': cfg.smb_domain, + 'server': cfg.smb_server, + 'share': cfg.smb_share, + 'mountpoint': mountpoint, + 'dir_mode': cfg.smb_dir_mode, + 'file_mode': cfg.smb_file_mode, + 'iocharset': cfg.smb_iocharset, + 'log': cfg.smb_log, + } + env = os.environ.copy() + if login: + try: + os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us! + except OSError, err: + pass + env['PASSWD'] = password.encode(cfg.smb_coding) + subprocess.call(cmd.encode(cfg.smb_coding), env=env, shell=True) + return user_obj, True +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/sslclientcert.py Sat Jun 10 10:12:59 2006 +0200 @@ -0,0 +1,71 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - SSL client certificate authentication + + Currently not supported for Twisted web server, but only for web servers + setting SSL_CLIENT_* environment (e.g. Apache). + + @copyright: 2006 by MoinMoin:ThomasWaldmann, + 2003 by Martin v. Löwis + @license: GNU GPL, see COPYING for details. +""" + +from MoinMoin import user +from MoinMoin.request import TWISTED + +def sslclientcert(request, **kw): + """ authenticate via SSL client certificate """ + user_obj = kw.get('user_obj') + u = None + changed = False + # check if we are running Twisted + if isinstance(request, TWISTED.Request): + return user_obj, True # not supported if we run twisted + # Addendum: this seems to need quite some twisted insight and coding. + # A pointer i got on #twisted: divmod's vertex.sslverify + # If you really need this, feel free to implement and test it and + # submit a patch if it works. + else: + env = request.env + if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS': + # if we only want to accept some specific CA, do a check like: + # if env.get('SSL_CLIENT_I_DN_OU') == "http://www.cacert.org" + email = env.get('SSL_CLIENT_S_DN_Email', '') + email_lower = email.lower() + commonname = env.get('SSL_CLIENT_S_DN_CN', '') + commonname_lower = commonname.lower() + if email_lower or commonname_lower: + for uid in user.getUserList(request): + u = user.User(request, uid, + auth_method='sslclientcert', auth_attribs=()) + if email_lower and u.email.lower() == email_lower: + u.auth_attribs = ('email', 'password') + #this is only useful if same name should be used, as + #commonname is likely no CamelCase WikiName + #if commonname_lower != u.name.lower(): + # u.name = commonname + # changed = True + #u.auth_attribs = ('email', 'name', 'password') + break + if commonname_lower and u.name.lower() == commonname_lower: + u.auth_attribs = ('name', 'password') + #this is only useful if same email should be used as + #specified in certificate. + #if email_lower != u.email.lower(): + # u.email = email + # changed = True + #u.auth_attribs = ('name', 'email', 'password') + break + else: + u = None + if u is None: + # user wasn't found, so let's create a new user object + u = user.User(request, name=commonname_lower, auth_username=commonname_lower) + + if u: + u.create_or_update(changed) + if u and u.valid: + return u, True + else: + return user_obj, True +
--- a/docs/CHANGES Sat Jun 10 00:24:50 2006 +0200 +++ b/docs/CHANGES Sat Jun 10 10:12:59 2006 +0200 @@ -65,7 +65,9 @@ * moved mailimport.py to mail/mailimport.py, moved util/mail.py to mail/sendmail.py * moved auth.py to auth/__init__.py, - moved util/sessionParser.py to auth/_PHPsessionParser.py + moved util/sessionParser.py to auth/_PHPsessionParser.py, + teared auth code into single modules under auth/* - moin_session handling + and the builting moin_login method are in auth/__init__.py. * added wikiutil.MimeType class (works internally with sanitized mime types because the official ones suck) * renamed parsers to module names representing sane mimetypes, e.g.: