@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
logging = log.getLogger(__name__)
+def connect(dbhost=None, dbport=None, dbname=None, dbuser=None, dbpass=None, **kwargs):
+ # This code was shamelessly stolen from
+ # django.db.backends.mysql.base.cursor
+ kwargs = {
+ 'charset': 'utf8',
+ 'use_unicode': False,
+ }
+ if dbuser:
+ kwargs['user'] = dbuser
+ if dbname:
+ kwargs['db'] = dbname
+ if dbpass:
+ kwargs['passwd'] = dbpass
+ if dbhost.startswith('/'):
+ kwargs['unix_socket'] = dbhost
+ elif dbhost:
+ kwargs['host'] = dbhost
+ if dbport:
+ kwargs['port'] = int(dbport)
+
+ # End stolen code
+
+ try:
+ conn = MySQLdb.connect (**kwargs)
+ except:
+ import sys
+ import traceback
+ info = sys.exc_info()
+ logging.error("phpbb: authentication failed due to exception connecting to DB, traceback follows...")
+ logging.error(''.join(traceback.format_exception(*info)))
+ return False
+
+ return conn
+
+
class PhpbbGroupsBackend(LazyGroupsBackend):
class PhpbbGroup(LazyGroup):
"""
"""
pass
- def __init__(self, request, auth):
+ def __init__(self, request, **kwargs):
super(LazyGroupsBackend, self).__init__(request)
- self.auth = auth
self.request = request
+ self.dbconfig = kwargs
def __iter__(self):
"""
Return a list of group names.
"""
return self.list_query("SELECT group_name \
- FROM `%sgroups` \
- WHERE group_single_user = 0"
- % self.auth.phpbb_prefix)
+ FROM `%sgroups`"
+ % self.dbconfig['phpbb_prefix'])
def __contains__(self, group_name):
"""
return self.single_query("SELECT EXISTS ( \
SELECT * \
FROM `%sgroups` \
- WHERE group_single_user = 0 \
- AND group_name=%%s)" % self.auth.phpbb_prefix,
+ WHERE group_name=%%s)" % self.dbconfig['phpbb_prefix'],
group_name)
def __getitem__(self, group_name):
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"
- % (self.auth.phpbb_prefix, self.auth.phpbb_prefix, self.auth.phpbb_prefix),
+ AND ug.user_pending = 0 AND g.group_name = %%s"
+ % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
group_name)
def _group_has_member(self, group_name, member):
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.auth.phpbb_prefix, self.auth.phpbb_prefix, self.auth.phpbb_prefix),
+ % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
(group_name, member))
def groups_with_member(self, member):
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"
- % (self.auth.phpbb_prefix, self.auth.phpbb_prefix, self.auth.phpbb_prefix),
+ AND ug.user_pending = 0 AND u.username = %%s"
+ % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
member)
def single_query(self, *args):
conn = None
cursor = None
try:
- conn = self.auth.connect(self.request)
+ conn = connect(**self.dbconfig)
cursor = conn.cursor()
cursor.execute(*args)
conn = None
cursor = None
try:
- conn = self.auth.connect(self.request)
+ conn = connect(**self.dbconfig)
cursor = conn.cursor()
cursor.execute(*args)
logout_possible = True
login_inputs = ['username', 'password']
- def __init__(self, name='phpbb', dbhost=None, dbuser=None, dbpass=None, dbname=None, dbport=None, phpbb_prefix='', hint=None):
+ def __init__(self, name='phpbb', hint=None, **kwargs):
"""
Authenticate using credentials from a phpbb database
The hint parameter is a snippet of HTML that is displayed below the login form.
"""
- self.dbhost = dbhost
- self.dbuser = dbuser
- self.dbpass = dbpass
- self.dbname = dbname
- self.dbport = dbport
- self.phpbb_prefix = phpbb_prefix
+ self.dbconfig = kwargs
self.name = name
self.hint = hint
+ self.hash = phpass.PasswordHash()
- # Create a "constructor" to create a phpbb_groups instance, while
- # passing ourselves to it.
- self.groups_backend = lambda config, request: PhpbbGroupsBackend(request, self)
-
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 = self.connect(request)
+ conn = connect(**self.dbconfig)
if not conn:
return False
# 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.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):
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
-
- def connect(self, request):
- # This code was shamelessly stolen from
- # django.db.backends.mysql.base.cursor
- kwargs = {
- 'charset': 'utf8',
- 'use_unicode': False,
- }
- if self.dbuser:
- kwargs['user'] = self.dbuser
- if self.dbname:
- kwargs['db'] = self.dbname
- if self.dbpass:
- kwargs['passwd'] = self.dbpass
- if self.dbhost.startswith('/'):
- kwargs['unix_socket'] = self.dbhost
- elif self.dbhost:
- kwargs['host'] = self.dbhost
- if self.dbport:
- kwargs['port'] = int(self.dbport)
-
- # End stolen code
-
- try:
- conn = MySQLdb.connect (**kwargs)
- except:
- import sys
- import traceback
- info = sys.exc_info()
- logging.error("phpbb_login: authentication failed due to exception connecting to DB, traceback follows...")
- logging.error(''.join(traceback.format_exception(*info)))
- return False
-
- return conn
+ return (False, False)
def login(self, request, user_obj, **kw):
"""
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)
""" Return a snippet of HTML that is displayed with the login form. """
return self.hint
+def setup(**kwargs):
+ """
+ Setup the phpbb backend. Takes the following keyword arguments:
+ dbhost -- The database server host
+ dbport -- The database server portname
+ dbname -- The database name
+ dbuser -- The username to log in
+ dbpass -- The password to log in
+ phpbb_prefix -- The table name prefix used for this phpbb installation
+ name -- The name to use for the auth backend
+ hint -- A hint to show in the login interface (HTML string)
+
+ This function can be called multiple times to create backends for
+ different phpbb installations
+
+ Returns a tuple (auth, groups) containing an (instantiated) auth backend
+ and a groups backend (constructor). These can be put directly into the
+ "auth" (as part of the list) and "groups" (directly) config directives.
+
+ e.g.,
+
+ class FarmConfig:
+ (phpbb_auth, phpbb_groups) = phpbb.setup(...)
+ auth = [phpbb_auth]
+ groups = phpbb_groups
+
+ (actual configuration parameters to setup() are omitted in this example)
+ """
+
+ # Create a "constructor" to create a phpbb_groups instance, while
+ # passing ourselves to it.
+ groups = lambda config, request: PhpbbGroupsBackend(request, **kwargs)
+ # Create an instantiated auth backend.
+ auth = PhpbbAuth(**kwargs)
+
+ return (auth, groups)
+
# vim: set sw=4 expandtab sts=4:vim