changeset 301:6b02d608c5f4

Added SSO support/auth module for PHP based applications. imported from: moin--main--1.5--patch-305
author Alexander Schremmer <alex@alexanderweb.de.tla>
date Fri, 09 Dec 2005 20:12:10 +0000
parents 174cba552bd9
children c079e9a1d613
files MoinMoin/auth.py MoinMoin/util/sessionParser.py docs/CHANGES
diffstat 3 files changed, 236 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/auth.py	Thu Dec 08 20:04:35 2005 +0000
+++ b/MoinMoin/auth.py	Fri Dec 09 20:12:10 2005 +0000
@@ -39,6 +39,7 @@
     method that authentified the user.
     
     @copyright: (c) Bastian Blank, Florian Festi, Thomas Waldmann
+    @copyright: MoinMoin:AlexanderSchremmer
     @license: GNU GPL, see COPYING for details.
 """
 
@@ -78,14 +79,15 @@
     return None, True
 
 
-"""
-   idea: maybe we should call back to the request object like:
-         username, password, authenticated, authtype = request.getUserPassAuth()
-	 WhoEver   geheim    false          basic      (twisted, doityourself pw check)
-	 WhoEver   None      true           basic/...  (apache)
-	 
-         thus, the server specific code would stay in request object implementation.
-"""
+#
+#   idea: maybe we should call back to the request object like:
+#         username, password, authenticated, authtype = request.getUserPassAuth()
+#	 WhoEver   geheim    false          basic      (twisted, doityourself pw check)
+#	 WhoEver   None      true           basic/...  (apache)
+#	 
+#        thus, the server specific code would stay in request object implementation.
+#
+#     THIS IS NOT A WIKI PAGE ;-)
 	 
 def http(request, **kw):
     """ authenticate via http basic/digest/ntlm auth """
@@ -218,3 +220,81 @@
         pass
         # XXX redirect to homewiki
 
+
+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):
+        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
+        import urllib
+        from MoinMoin.user import User
+    
+        from MoinMoin.util import sessionParser
+    
+        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 = sessionParser.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 None, 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, False # return user object and stop processing auth method list
+        return None, True # return None and continue with next method in auth list
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/util/sessionParser.py	Fri Dec 09 20:12:10 2005 +0000
@@ -0,0 +1,143 @@
+"""
+    MoinMoin - Parsing of PHP session files
+
+    @copyright: 2005 by MoinMoin:AlexanderSchremmer
+        - Thanks to Spreadshirt
+    @license: GNU GPL, see COPYING for details.
+"""
+
+#Known minor bugs/questions/ideas:
+#How does object demarshalling work?
+#The order of the python dictionaries is not stable compared to the PHP arrays
+#The loader does not check the owner of the files, so be aware of faked session
+#files.
+
+import os
+from MoinMoin import wikiutil
+
+s_prefix = "sess_"
+s_path = "/tmp"
+
+class UnknownObject(object):
+    """ Used in the return value if the input data could not be parsed. """
+    def __init__(self, pos):
+        self.pos = pos
+
+    def __repr__(self):
+        return "<Unknown object at pos %i>" % self.pos
+
+def transformList(items):
+    """ Transforms a list [1, 2, 3, 4, ...] into a
+        [(1, 2), (3, 4), ...] generator. """
+    for i in xrange(0, len(items), 2):
+        yield (items[i], items[i+1])
+    raise StopIteration
+
+def parseValue(string, start=0):
+    """ Parses the inner structure. """
+    #print "Parsing %r" % (string[start:], )
+
+    val_type = string[start]
+    header_end = string.find(':', 3+start)
+    if header_end != -1:
+        first_data = string[start+2:header_end]
+    else:
+        first_data = None
+    
+    #print "Saw type %r, first_data is %r." % (val_type, first_data)
+    if val_type == 'a': # array (in Python rather a mixture of a list and a dict)
+        i = 0
+        items = []
+        
+        current_pos = header_end+2
+        data = string
+        while i != (int(first_data) * 2):
+            item, current_pos = parseValue(data, current_pos)
+            items.append(item)
+            i += 1
+            current_pos += 1
+        
+        t_list = list(transformList(items))
+        try:
+            result = dict(t_list) # note that dict does not retain the order
+        except TypeError:
+            result = list(t_list)
+            #print "Warning, could not convert to dict: %r" %  (result, )
+        return result, current_pos
+    
+    if val_type == 's': # string
+        current_pos = header_end+2
+        end = current_pos + int(first_data)
+        data = string[current_pos:end]
+        current_pos = end+1
+        if data.startswith("a:"): #Sometimes, arrays are marshalled as strings.
+            try:
+                data = parseValue(data, 0)[0]
+            except ValueError: #Hmm, wrongly guessed. Just an ordinary string
+                pass
+        return data, current_pos
+
+    if val_type in ('i', 'b'): # integer or boolean
+        current_pos = start+2
+        str_buffer = ""
+        while current_pos != len(string):
+            cur_char = string[current_pos]
+            if cur_char.isdigit() or cur_char == "-":
+                str_buffer += cur_char
+            else:
+                cast = (val_type == 'i') and int or (lambda x: bool(int(x)))
+                return cast(str_buffer), current_pos
+            current_pos += 1
+
+    if val_type == "N": # Null, called None in Python
+        return None, start+1
+        
+    return UnknownObject(start), start+1
+
+def parseSession(boxed):
+    """ Parses the outer structure that is similar to a dict. """
+    current_pos = 0
+    session_dict = {}
+    while current_pos < len(boxed):
+        name_end = boxed.find("|", current_pos)
+        name = boxed[current_pos:name_end]
+        current_pos = name_end+1
+        data, current_pos = parseValue(boxed, current_pos)
+        current_pos += 1
+        session_dict[name] = data
+
+    return session_dict
+
+def loadSession(key, path=s_path, prefix=s_prefix):
+    """ Loads a particular session from the directory. The key needs to be the
+        session id. """
+    key = key.lower()
+    filename = os.path.join(path, prefix + wikiutil.taintfilename(key))
+
+    try:
+        f = open(filename, "rb")
+    except IOError, e:
+        if e.errno == 2:
+            return None # session does not exist
+        else:
+            raise
+
+    blob = f.read()
+    f.close()
+    return parseSession(blob)
+
+def listSessions(path=s_path, prefix=s_prefix):
+    """ Lists all sessions in a particular directory. """
+    return [os.path.basename(x).replace(s_prefix, '') for x in os.listdir(s_path)
+            if x.startswith(s_prefix)]
+
+if __name__ == '__main__':
+    # testing code
+    import time
+    a=time.clock()
+    
+    #print s
+    p_s = loadSession("...")
+    import pprint; pprint.pprint(p_s)
+    print time.clock() - a
+    print listSessions()
--- a/docs/CHANGES	Thu Dec 08 20:04:35 2005 +0000
+++ b/docs/CHANGES	Fri Dec 09 20:12:10 2005 +0000
@@ -2,6 +2,11 @@
 ========================
 
 Version 1.5.0current:
+  Authentication:
+    * Added SSO module for PHP based apps. Currently supported: eGroupware 1.2.
+      No need to login in two systems anymore - MoinMoin will read the PHP session
+      files.
+
   Other changes:
     * Made it easier for auth methods needing a user interface (like ldap or
       mysql stuff). Unlike http auth, they usually need some "login form".