1 # -*- coding: iso-8859-1 -*-
3 MoinMoin - auth plugin doing a check against MySQL db
5 @copyright: 2008 Matthijs Kooijman
6 @license: GNU GPL, see COPYING for details.
8 This plugin allows authentication (use accounts and password) and
9 authorization (use groups) against a phpbb Mysql database.
11 To use this plugin, you should put it in a place where the config python
12 file can "see" it (somewhere in your pythonpath, or in the same dir as the
13 config file). Then import the setup function and call it.
19 (phpbb_auth, phpbb_groups) = phpbb.setup(...)
25 # Password encryption module. Python port of the method used by phpbb3.
27 from MoinMoin import user
28 from MoinMoin.auth import BaseAuth, ContinueLogin
29 from MoinMoin.datastruct.backends import LazyGroupsBackend, LazyGroup
30 from MoinMoin import log
31 logging = log.getLogger(__name__)
34 def connect(dbhost=None, dbport=None, dbname=None, dbuser=None, dbpass=None, **kwargs):
35 # This code was shamelessly stolen from
36 # django.db.backends.mysql.base.cursor
42 kwargs['user'] = dbuser
46 kwargs['passwd'] = dbpass
47 if dbhost.startswith('/'):
48 kwargs['unix_socket'] = dbhost
50 kwargs['host'] = dbhost
52 kwargs['port'] = int(dbport)
57 conn = MySQLdb.connect (**kwargs)
62 logging.error("phpbb: authentication failed due to exception connecting to DB, traceback follows...")
63 logging.error(''.join(traceback.format_exception(*info)))
69 class PhpbbGroupsBackend(LazyGroupsBackend):
70 class PhpbbGroup(LazyGroup):
76 def __init__(self, request, **kwargs):
77 super(LazyGroupsBackend, self).__init__(request)
79 self.request = request
80 self.dbconfig = kwargs
84 Return a list of group names.
86 return self.list_query("SELECT group_name \
88 % self.dbconfig['phpbb_prefix'])
90 def __contains__(self, group_name):
92 Does a group with the given name exist?
94 return self.single_query("SELECT EXISTS ( \
97 WHERE group_name=%%s)" % self.dbconfig['phpbb_prefix'],
100 def __getitem__(self, group_name):
102 Get the group with the given name.
104 return self.PhpbbGroup(self.request, group_name, self)
106 def _iter_group_members(self, group_name):
108 Get all member names for the given group. This is called by
111 return self.list_query ("SELECT username \
112 FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
113 WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
114 AND ug.user_pending = 0 AND g.group_name = %%s"
115 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
118 def _group_has_member(self, group_name, member):
120 Does the group with the given name have a member with the given name?
121 This is called by LazyGroup.__contains__.
123 return self.single_query ("SELECT EXISTS( \
125 FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
126 WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
127 AND ug.user_pending = 0 \
128 AND g.group_name = %%s AND u.username = %%s)"
129 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
130 (group_name, member))
132 def groups_with_member(self, member):
134 Return the group names for all groups that have a member with the
137 return self.list_query ("SELECT g.group_name \
138 FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
139 WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
140 AND ug.user_pending = 0 AND u.username = %%s"
141 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
144 def single_query(self, *args):
146 Runs an SQL query, that returns single row with a single column.
147 Returns just that single result.
152 conn = connect(**self.dbconfig)
153 cursor = conn.cursor()
154 cursor.execute(*args)
156 return cursor.fetchone()[0]
163 def list_query(self, *args):
165 Runs an SQL query, that returns any number of single-column rows.
166 Returns the results as a list of single values
171 conn = connect(**self.dbconfig)
172 cursor = conn.cursor()
173 cursor.execute(*args)
183 class PhpbbAuth(BaseAuth):
184 logout_possible = True
185 login_inputs = ['username', 'password']
187 def __init__(self, name='phpbb', hint=None, **kwargs):
189 Authenticate using credentials from a phpbb database
191 The name parameter should be unique among all authentication methods.
193 The hint parameter is a snippet of HTML that is displayed below the login form.
195 self.dbconfig = kwargs
198 self.hash = phpass.PasswordHash()
200 def check_login(self, request, username, password):
201 """ Checks the given username password combination. Returns the
202 real username and corresponding emailaddress, or (False, False)
203 if authentication failed. Username checks are case insensitive,
204 so the real username (with the real casing) is returned (since
205 ACL checks _are_ case sensitive).
207 conn = connect(**self.dbconfig)
212 # Get some data. Note that we interpolate the prefix ourselves, since
213 # letting the mysql library do it only works with values (it adds ''
214 # automatically). Note also that this allows possible SQL injection
215 # through the phpbb_prefix variable, but that should be a trusted
217 # Finally note that by default, the phpbb database specifies a
218 # case insensitive collaction for the username field, so
219 # usernames are checked in case insensitive manner.
220 cursor = conn.cursor ()
221 cursor.execute ("SELECT user_password,user_email,username FROM `%susers` WHERE username=%%s" % self.dbconfig['phpbb_prefix'], username)
224 if (cursor.rowcount == 0):
229 row = cursor.fetchone()
232 if self.hash.check_password(password, row[0]):
233 return (row[1], row[2])
235 return (False, False)
237 def login(self, request, user_obj, **kw):
239 Handle a login. Called by moinmoin.
242 username = kw.get('username')
243 password = kw.get('password')
245 logging.debug("phpbb_login: Trying to log in, username=%r " % (username))
247 # simply continue if something else already logged in
249 if user_obj and user_obj.valid:
250 return ContinueLogin(user_obj)
252 # Deny empty username or passwords
253 if not username or not password:
254 return ContinueLogin(user_obj)
256 (email, real_username) = self.check_login(request, username, password)
260 logging.debug("phpbb_login: authentication failed for %s" % (username))
261 return ContinueLogin(user_obj)
263 logging.debug("phpbb_login: authenticated %s (email %s, real username %s)" % (username, email, real_username))
265 # We use the username from the database (real_username)
266 # here, since the username from the request might have
267 # "wrong" casing (and ACL checks are case sensitive).
268 u = user.User(request, auth_username=real_username, auth_method=self.name, auth_attribs=('name', 'password', 'email'))
270 #u.remember_me = 0 # 0 enforces cookie_lifetime config param
271 u.create_or_update(True)
273 return ContinueLogin(u)
277 info = sys.exc_info()
278 logging.error("phpbb_login: authentication failed due to unexpected exception, traceback follows...")
279 logging.error(''.join(traceback.format_exception(*info)))
280 return ContinueLogin(user_obj)
282 def login_hint(self, request):
283 """ Return a snippet of HTML that is displayed with the login form. """
288 Setup the phpbb backend. Takes the following keyword arguments:
289 dbhost -- The database server host
290 dbport -- The database server portname
291 dbname -- The database name
292 dbuser -- The username to log in
293 dbpass -- The password to log in
294 phpbb_prefix -- The table name prefix used for this phpbb installation
295 name -- The name to use for the auth backend
296 hint -- A hint to show in the login interface (HTML string)
298 This function can be called multiple times to create backends for
299 different phpbb installations
301 Returns a tuple (auth, groups) containing an (instantiated) auth backend
302 and a groups backend (constructor). These can be put directly into the
303 "auth" (as part of the list) and "groups" (directly) config directives.
308 (phpbb_auth, phpbb_groups) = phpbb.setup(...)
310 groups = phpbb_groups
312 (actual configuration parameters to setup() are omitted in this example)
315 # Create a "constructor" to create a phpbb_groups instance, while
316 # passing ourselves to it.
317 groups = lambda config, request: PhpbbGroupsBackend(request, **kwargs)
318 # Create an instantiated auth backend.
319 auth = PhpbbAuth(**kwargs)
321 return (auth, groups)
323 # vim: set sw=4 expandtab sts=4:vim