phpbb: Make sure PhpbbAuth::check_login always returns a tuple.
[matthijs/projects/wipi.git] / conf / auth / phpbb.py
index 0558d53aa443791d876a6eff4b84f43ce53c2d56..54763b3558731a9db66e165c7583a145a973ea2c 100644 (file)
@@ -4,10 +4,26 @@
 
     @copyright: 2008 Matthijs Kooijman
     @license: GNU GPL, see COPYING for details.
+
+    This plugin allows authentication (use accounts and password) and
+    authorization (use groups) against a phpbb Mysql database.
+
+    To use this plugin, you should put it in a place where the config python
+    file can "see" it (somewhere in your pythonpath, or in the same dir as the
+    config file). Then import the setup function and call it.
+
+    For example:
+
+    class FarmConfig:
+        import phpbb
+        (phpbb_auth, phpbb_groups) = phpbb.setup(...)
+        auth = [phpbb_auth]
+        groups = phpbb_groups
 """
 
 import MySQLdb
-import md5
+# Password encryption module. Python port of the method used by phpbb3.
+import phpass
 from MoinMoin import user
 from MoinMoin.auth import BaseAuth, ContinueLogin
 from MoinMoin.datastruct.backends import LazyGroupsBackend, LazyGroup
@@ -68,8 +84,7 @@ class PhpbbGroupsBackend(LazyGroupsBackend):
         Return a list of group names.
         """
         return self.list_query("SELECT group_name \
-                                FROM `%sgroups` \
-                                WHERE group_single_user = 0"
+                                FROM `%sgroups`"
                                 % self.dbconfig['phpbb_prefix'])
 
     def __contains__(self, group_name):
@@ -79,8 +94,7 @@ class PhpbbGroupsBackend(LazyGroupsBackend):
         return self.single_query("SELECT EXISTS ( \
                                       SELECT * \
                                       FROM `%sgroups` \
-                                      WHERE group_single_user = 0 \
-                                            AND group_name=%%s)" % self.dbconfig['phpbb_prefix'],
+                                      WHERE group_name=%%s)" % self.dbconfig['phpbb_prefix'],
                                  group_name)
 
     def __getitem__(self, group_name):
@@ -97,8 +111,7 @@ class PhpbbGroupsBackend(LazyGroupsBackend):
         return self.list_query ("SELECT username \
                                  FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g  \
                                  WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
-                                       AND ug.user_pending = 0 AND g.group_single_user = 0 \
-                                       AND g.group_name = %%s"
+                                       AND ug.user_pending = 0 AND g.group_name = %%s"
                                  % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
                                 group_name)
 
@@ -111,7 +124,7 @@ class PhpbbGroupsBackend(LazyGroupsBackend):
                                        SELECT * \
                                        FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
                                        WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
-                                             AND ug.user_pending = 0 AND g.group_single_user = 0 \
+                                             AND ug.user_pending = 0 \
                                              AND g.group_name = %%s AND u.username = %%s)"
                                    % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
                                   (group_name, member))
@@ -124,8 +137,7 @@ class PhpbbGroupsBackend(LazyGroupsBackend):
         return self.list_query ("SELECT g.group_name \
                                  FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
                                  WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
-                                       AND ug.user_pending = 0 AND g.group_single_user = 0 \
-                                       AND u.username = %%s"
+                                       AND ug.user_pending = 0 AND u.username = %%s"
                                 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
                                 member)
 
@@ -183,37 +195,44 @@ class PhpbbAuth(BaseAuth):
         self.dbconfig = kwargs
         self.name    = name
         self.hint    = hint
+        self.hash    = phpass.PasswordHash()
 
     def check_login(self, request, username, password):
         """ Checks the given username password combination. Returns the
-        corresponding emailaddress, or False if authentication failed.
+        real username and corresponding emailaddress, or (False, False)
+        if authentication failed. Username checks are case insensitive,
+        so the real username (with the real casing) is returned (since
+        ACL checks _are_ case sensitive).
         """
         conn = connect(**self.dbconfig)
 
         if not conn:
-            return False
+            return (False, False)
 
         # Get some data. Note that we interpolate the prefix ourselves, since
         # letting the mysql library do it only works with values (it adds ''
         # automatically). Note also that this allows possible SQL injection
         # through the phpbb_prefix variable, but that should be a trusted
         # value anyway.
+        # Finally note that by default, the phpbb database specifies a
+        # case insensitive collaction for the username field, so
+        # usernames are checked in case insensitive manner.
         cursor = conn.cursor ()
-        cursor.execute ("SELECT user_password,user_email FROM `%susers` WHERE username=%%s" % self.dbconfig['phpbb_prefix'], username)
+        cursor.execute ("SELECT user_password,user_email,username FROM `%susers` WHERE LOWER(username)=LOWER(%%s)" % self.dbconfig['phpbb_prefix'], username)
 
         # No data? No login.
         if (cursor.rowcount == 0):
             conn.close()
-            return False
+            return (False, False)
        
         # Check password
         row = cursor.fetchone()
         conn.close()
 
-        if (md5.new(password).hexdigest() == row[0]):
-            return row[1]
+        if self.hash.check_password(password, row[0]):
+            return (row[1], row[2])
         else:
-            return False
+            return (False, False)
 
     def login(self, request, user_obj, **kw):
         """
@@ -234,16 +253,19 @@ class PhpbbAuth(BaseAuth):
             if not username or not password:
                 return ContinueLogin(user_obj)
 
-            email = self.check_login(request, username, password)
+            (email, real_username) = self.check_login(request, username, password)
             
             # Login incorrect
             if (not email):
                 logging.debug("phpbb_login: authentication failed for %s" % (username))
                 return ContinueLogin(user_obj)
 
-            logging.debug("phpbb_login: authenticated %s (email %s)" % (username, email))
+            logging.debug("phpbb_login: authenticated %s (email %s, real username %s)" % (username, email, real_username))
 
-            u = user.User(request, auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'email'))
+            # We use the username from the database (real_username)
+            # here, since the username from the request might have
+            # "wrong" casing (and ACL checks are case sensitive).
+            u = user.User(request, auth_username=real_username, auth_method=self.name, auth_attribs=('name', 'password', 'email'))
             u.email = email
             #u.remember_me = 0 # 0 enforces cookie_lifetime config param
             u.create_or_update(True)