changeset 450:215d66f88732

added ldap_login and smb_mount auth plugins + sample config imported from: moin--main--1.5--patch-454
author Thomas Waldmann <tw@waldmann-edv.de>
date Sat, 18 Feb 2006 16:58:21 +0000
parents 8ec16f62e989
children f264539ad649
files ChangeLog MoinMoin/auth.py docs/CHANGES wiki/config/more_samples/ldap_smb_farmconfig.py
diffstat 4 files changed, 411 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Feb 18 16:12:14 2006 +0000
+++ b/ChangeLog	Sat Feb 18 16:58:21 2006 +0000
@@ -2,6 +2,28 @@
 # arch-tag: automatic-ChangeLog--arch@arch.thinkmo.de--2003-archives/moin--main--1.5
 #
 
+2006-02-18 17:58:21 GMT	Thomas Waldmann <tw@waldmann-edv.de>	patch-454
+
+    Summary:
+      added ldap_login and smb_mount auth plugins + sample config
+    Revision:
+      moin--main--1.5--patch-454
+
+    added ldap_login and smb_mount auth plugins + sample config
+    
+
+    new files:
+     wiki/config/more_samples/.arch-ids/=id
+     wiki/config/more_samples/.arch-ids/ldap_smb_farmconfig.py.id
+     wiki/config/more_samples/ldap_smb_farmconfig.py
+
+    modified files:
+     ChangeLog MoinMoin/auth.py docs/CHANGES
+
+    new directories:
+     wiki/config/more_samples wiki/config/more_samples/.arch-ids
+
+
 2006-02-18 17:12:14 GMT	Alexander Schremmer <alex@alexanderweb.de.tla>	patch-453
 
     Summary:
--- a/MoinMoin/auth.py	Sat Feb 18 16:12:14 2006 +0000
+++ b/MoinMoin/auth.py	Sat Feb 18 16:58:21 2006 +0000
@@ -38,8 +38,8 @@
     It also gives a kw arg "auth_method" that tells the name of the auth
     method that authentified the user.
     
-    @copyright: (c) Bastian Blank, Florian Festi, Thomas Waldmann
-    @copyright: MoinMoin:AlexanderSchremmer
+    @copyright: 2005-2006 Bastian Blank, Florian Festi, Thomas Waldmann
+    @copyright: 2005-2006 MoinMoin:AlexanderSchremmer
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -183,6 +183,141 @@
     else:
         return None, True
 
+
+def smb_mount(request, **kw):
+    """ 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')
+    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 the smb share
+    if not login:
+        return None, True
+    
+    import os, pwd, subprocess
+    web_username = cfg.smb_dir_user
+    web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
+    mountpoint = cfg.smb_mountpoint % username
+    mountcmd = 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" % {
+        '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,
+    }
+    try:
+        os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
+    except OSError, err:
+        pass
+    env = os.environ.copy()
+    env['PASSWD'] = password.encode(cfg.smb_coding)
+    subprocess.call(mountcmd.encode(cfg.smb_coding), env=env, shell=True)
+    return None, 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')
+    
+    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, thus we return None, True.
+    if not login and not logout:
+        return None, 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 None, 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=password, 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 None, 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
     # TODO use tuples as return value
--- a/docs/CHANGES	Sat Feb 18 16:12:14 2006 +0000
+++ b/docs/CHANGES	Sat Feb 18 16:58:21 2006 +0000
@@ -44,6 +44,8 @@
     * We check cfg.superuser to be a list of user names (as documented) and
       deny superuser access if it is not. This avoids security issues by
       wrong configuration.
+    * ldap_login and smb_mount auth methods, see MoinMoin/auth.py and
+      wiki/config/more_samples/ldap_smb_farmconfig.py
 
   Bugfixes:
     * cookie_lifetime didn't work comfortable for low values. The cookie was
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/wiki/config/more_samples/ldap_smb_farmconfig.py	Sat Feb 18 16:58:21 2006 +0000
@@ -0,0 +1,250 @@
+# -*- coding: iso-8859-1 -*-
+# IMPORTANT! This encoding (charset) setting MUST be correct! If you live in a
+# western country and you don't know that you use utf-8, you probably want to
+# use iso-8859-1 (or some other iso charset). If you use utf-8 (a Unicode
+# encoding) you MUST use: coding: utf-8
+# That setting must match the encoding your editor uses when you modify the
+# settings below. If it does not, special non-ASCII chars will be wrong.
+
+"""
+    MoinMoin - Configuration for a wiki farm
+
+    This is a sample configuration for a farm using ldap and smb auth plugins.
+    
+    It works like this:
+    * user logs in via moin's form on UserPreferences
+    * ldap_login plugin checks username/password against LDAP
+      * if username/password is ok for LDAP, the plugin creates a user profile
+        with up-to-date settings from ldap (name, alias, email and crypted
+        password) and just hands over to the next auth plugin...
+      * if username/password is not ok for LDAP, it does not store/update
+        a user profile, but also hands over to the next auth plugin.
+    * smb_mount plugin also gets username/password and uses it to mount another
+      server's share on some mountpoint (using access rights of username,
+      authenticating using password) on the wiki server (very special use only -
+      if you don't need it, don't use it).
+    * moin_cookie finally gets username/password and uses it to load the
+      user's profile, set a cookie for subsequent requests and return a user
+      object.
+
+"""
+
+# Wikis in your farm --------------------------------------------------
+
+# If you run multiple wikis, you need this list of pairs (wikiname, url
+# regular expression). moin processes that list and tries to match the
+# regular expression against the URL of this request - until it matches.
+# Then it loads the <wikiname>.py config for handling that request.
+
+# Important:
+#  * the left part is the wikiname enclosed in double quotes
+#  * the left part must be a valid python module name, so better use only
+#    lower letters "a-z" and "_". Do not use blanks or "-" there!!!
+#  * the right part is the url re, use r"..." for it
+#  * the right part does NOT include "http://" nor "https://" at the beginning
+#  * in the right part ".*" means "everything". Just "*" does not work like
+#    for filenames on the shell / commandline, you must use ".*" as it is a RE.
+#  * in the right part, "^" means "beginning" and "$" means "end"
+
+wikis = [
+    # Standalone server needs the port e.g. localhost:8000
+    # Twisted server can now use the port, too.
+    
+    # wikiname,     url regular expression (no protocol)
+    # ---------------------------------------------------------------
+    ("info1",  r"^info1.example.org/.*$"),
+    ("info2",  r"^info2.example.org/.*$"),
+]
+
+
+# Common configuration for all wikis ----------------------------------
+
+# Everything that should be configured the same way should go here,
+# anything else that should be different should go to the single wiki's
+# config.
+# In that single wiki's config, we will use the class FarmConfig we define
+# below as the base config settings and only override what's different.
+#
+# In exactly the same way, we first include MoinMoin's Config Defaults here -
+# this is to get everything to sane defaults, so we need to change only what
+# we like to have different:
+
+from MoinMoin.multiconfig import DefaultConfig
+
+# Now we subclass this DefaultConfig. This means that we inherit every setting
+# from the DefaultConfig, except those we explicitely define different.
+
+class FarmConfig(DefaultConfig):
+
+    from MoinMoin import auth
+    auth = [auth.ldap_login, auth.smb_mount, auth.moin_cookie]
+    
+    import ldap
+    ldap_uri = 'ldap://ad.example.org' # ldap / active directory server URI
+    
+    #we can either use some fixed user and password for binding to LDAP
+    #ldap_binddn = 'binduser@example.org'
+    #ldap_bindpw = 'secret'
+    
+    #or we can use the username and password we got from the user:
+    ldap_binddn = '%(username)s@example.org' # DN we use for first bind
+    ldap_bindpw = '%(password)s' # password we use for first bind
+    
+    ldap_base = 'ou=SOMEUNIT,dc=example,dc=org' # base DN we use for searching
+    ldap_scope = ldap.SCOPE_SUBTREE # scope of the search we do
+    ldap_name_attribute = 'sAMAccountName' # ldap attribute we get the user name from
+    ldap_email_attribute = 'mail' # ldap attribute we get the email address from
+    ldap_coding = 'utf-8' # coding used for ldap queries and result values
+    ldap_timeout = 10 # how long we wait for the ldap server [s]
+    ldap_verbose = True # if True, put lots of LDAP debug info into the log
+    cookie_lifetime = 1 # 1 hour after last access ldap login is required again
+    user_autocreate = True
+
+    smb_server = "smb.example.org" # smb server name
+    smb_domain = 'DOMAIN' # smb domain name
+    smb_share = 'FILESHARE' # smb share we mount
+    smb_mountpoint = u'/mnt/wiki/%s' # %s -> username we use for mounting
+    smb_display_prefix = u"S:" # where //server/share is usually mounted for your windows users (display purposes only)
+    smb_dir_user = "wwwrun" # owner of the mounted directories
+    smb_dir_mode = "0700" # mode of the mounted directories
+    smb_file_mode = "0600" # mode of the mounted files
+    smb_iocharset = "iso8859-1" # "UTF-8" > cannot access needed shared library!
+    smb_coding = 'iso8859-1' # coding used for encoding the commandline for the mount command
+    smb_verbose = True # if True, put SMB debug info into log
+    smb_log = "/dev/null" # where we redirect mount command output to
+
+    # customize UserPreferences (optional)
+    user_checkbox_remove = [
+        'disabled', 'remember_me', 'edit_on_doubleclick', 'show_nonexist_qm',
+        'show_toolbar', 'show_topbottom', 'show_fancy_diff',
+        'wikiname_add_spaces', ]
+    user_checkbox_defaults = {'mailto_author':       0,
+                              'edit_on_doubleclick': 0,
+                              'remember_last_visit': 0,
+                              'show_nonexist_qm':    0,
+                              'show_page_trail':     1,
+                              'show_toolbar':        1,
+                              'show_topbottom':      0,
+                              'show_fancy_diff':     1,
+                              'wikiname_add_spaces': 0,
+                              'remember_me':         0,
+                              'want_trivial':        0,
+                             }
+    user_form_defaults = { # key: default
+        'name': '',
+        'aliasname': '',
+        'password': '',
+        'password2': '',
+        'email': '',
+        'css_url': '',
+        'edit_rows': "20",
+    }
+    user_form_disable = ['name', 'aliasname', 'email',]
+    user_form_remove = ['password', 'password2', 'css_url', 'logout', 'create', 'account_sendmail',]
+
+    # Critical setup  ---------------------------------------------------
+
+    # Misconfiguration here will render your wiki unusable. Check that
+    # all directories are accessible by the web server or moin server.
+
+    # If you encounter problems, try to set data_dir and data_underlay_dir
+    # to absolute paths.
+
+    # Where your mutable wiki pages are. You want to make regular
+    # backups of this directory.
+    data_dir = './data/'
+
+    # Where read-only system and help page are. You might want to share
+    # this directory between several wikis. When you update MoinMoin,
+    # you can safely replace the underlay directory with a new one. This
+    # directory is part of MoinMoin distribution, you don't have to
+    # backup it.
+    data_underlay_dir = './underlay/'
+
+    # This must be '/wiki' for twisted and standalone. For CGI, it should
+    # match your Apache Alias setting.
+    url_prefix = '/wiki'
+
+
+    # Security ----------------------------------------------------------
+
+    # This is checked by some rather critical and potentially harmful actions,
+    # like despam or PackageInstaller action:
+    #superuser = [u"AdminName", ]
+
+    # IMPORTANT: grant yourself admin rights! replace YourName with
+    # your user name. See HelpOnAccessControlLists for more help.
+    # All acl_rights_xxx options must use unicode [Unicode]
+    acl_rights_before = u"AdminGroup:admin,read,write,delete,revert"
+    acl_rights_default=u"EditorGroup:read,write.delete,revert ViewerGroup:read All:"
+
+    # Link spam protection for public wikis (uncomment to enable).
+    # Needs a reliable internet connection.
+    from MoinMoin.util.autoadmin import SecurityPolicy
+
+
+    # Mail --------------------------------------------------------------
+
+    # Configure to enable subscribing to pages (disabled by default) or
+    # sending forgotten passwords.
+
+    # SMTP server, e.g. "mail.provider.com" (empty or None to disable mail)
+    mail_smarthost = "mail.example.org"
+
+    # The return address, e.g u"Jürgen Wiki <noreply@mywiki.org>" [Unicode]
+    mail_from = u"wiki@example.org"
+
+    # "user pwd" if you need to use SMTP AUTH
+    mail_login = ""
+
+
+    # User interface ----------------------------------------------------
+
+    # Add your wikis important pages at the end. It is not recommended to
+    # remove the default links.  Leave room for user links - don't use
+    # more than 6 short items.
+    # You MUST use Unicode strings here, but you need not use localized
+    # page names for system and help pages, those will be used automatically
+    # according to the user selected language. [Unicode]
+    navi_bar = [
+        # If you want to show your page_front_page here:
+        u'%(page_front_page)s',
+        u'RecentChanges',
+        u'FindPage',
+        u'HelpContents',
+    ]
+
+    # The default theme anonymous or new users get
+    theme_default = 'modern'
+
+
+    # Language options --------------------------------------------------
+
+    # See http://moinmoin.wikiwikiweb.de/ConfigMarket for configuration in 
+    # YOUR language that other people contributed.
+
+    # The main wiki language, set the direction of the wiki pages
+    language_default = 'de'
+
+    # You must use Unicode strings here [Unicode]
+    page_category_regex = u'^Category[A-Z]'
+    page_dict_regex = u'[a-z]Dict$'
+    page_group_regex = u'[a-z]Group$'
+    page_template_regex = u'[a-z]Template$'
+
+    # Content options ---------------------------------------------------
+
+    # Show users hostnames in RecentChanges
+    show_hosts = 1
+
+    # Show the interwiki name (and link it to page_front_page) in the Theme,
+    # nice for farm setups or when your logo does not show the wiki's name.
+    show_interwiki = 1
+    logo_string = u''
+
+    # Enable graphical charts, requires gdchart.
+    #chart_options = {'width': 600, 'height': 300}
+
+    # interwiki map
+    #shared_intermap = ["/opt/moinfarm/common/intermap.txt", ]
+