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 corresponding emailaddress, or False if authentication failed.
206 conn = connect(**self.dbconfig)
211 # Get some data. Note that we interpolate the prefix ourselves, since
212 # letting the mysql library do it only works with values (it adds ''
213 # automatically). Note also that this allows possible SQL injection
214 # through the phpbb_prefix variable, but that should be a trusted
216 cursor = conn.cursor ()
217 cursor.execute ("SELECT user_password,user_email FROM `%susers` WHERE username=%%s" % self.dbconfig['phpbb_prefix'], username)
220 if (cursor.rowcount == 0):
225 row = cursor.fetchone()
228 if (md5.new(password).hexdigest() == row[0]):
233 def login(self, request, user_obj, **kw):
235 Handle a login. Called by moinmoin.
238 username = kw.get('username')
239 password = kw.get('password')
241 logging.debug("phpbb_login: Trying to log in, username=%r " % (username))
243 # simply continue if something else already logged in
245 if user_obj and user_obj.valid:
246 return ContinueLogin(user_obj)
248 # Deny empty username or passwords
249 if not username or not password:
250 return ContinueLogin(user_obj)
252 email = self.check_login(request, username, password)
256 logging.debug("phpbb_login: authentication failed for %s" % (username))
257 return ContinueLogin(user_obj)
259 logging.debug("phpbb_login: authenticated %s (email %s)" % (username, email))
261 u = user.User(request, auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'email'))
263 #u.remember_me = 0 # 0 enforces cookie_lifetime config param
264 u.create_or_update(True)
266 return ContinueLogin(u)
270 info = sys.exc_info()
271 logging.error("phpbb_login: authentication failed due to unexpected exception, traceback follows...")
272 logging.error(''.join(traceback.format_exception(*info)))
273 return ContinueLogin(user_obj)
275 def login_hint(self, request):
276 """ Return a snippet of HTML that is displayed with the login form. """
281 Setup the phpbb backend. Takes the following keyword arguments:
282 dbhost -- The database server host
283 dbport -- The database server portname
284 dbname -- The database name
285 dbuser -- The username to log in
286 dbpass -- The password to log in
287 phpbb_prefix -- The table name prefix used for this phpbb installation
288 name -- The name to use for the auth backend
289 hint -- A hint to show in the login interface (HTML string)
291 This function can be called multiple times to create backends for
292 different phpbb installations
294 Returns a tuple (auth, groups) containing an (instantiated) auth backend
295 and a groups backend (constructor). These can be put directly into the
296 "auth" (as part of the list) and "groups" (directly) config directives.
301 (phpbb_auth, phpbb_groups) = phpbb.setup(...)
303 groups = phpbb_groups
305 (actual configuration parameters to setup() are omitted in this example)
308 # Create a "constructor" to create a phpbb_groups instance, while
309 # passing ourselves to it.
310 groups = lambda config, request: PhpbbGroupsBackend(request, **kwargs)
311 # Create an instantiated auth backend.
312 auth = PhpbbAuth(**kwargs)
314 return (auth, groups)
316 # vim: set sw=4 expandtab sts=4:vim