phpbb: Add docstrings to a few methods.
[matthijs/projects/wipi.git] / conf / auth / phpbb.py
1 # -*- coding: iso-8859-1 -*-
2 """
3     MoinMoin - auth plugin doing a check against MySQL db
4
5     @copyright: 2008 Matthijs Kooijman
6     @license: GNU GPL, see COPYING for details.
7 """
8
9 import MySQLdb
10 import md5
11 from MoinMoin import user
12 from MoinMoin.auth import BaseAuth, ContinueLogin
13 from MoinMoin.datastruct.backends import LazyGroupsBackend, LazyGroup
14 from MoinMoin import log
15 logging = log.getLogger(__name__)
16
17
18 class PhpbbGroupsBackend(LazyGroupsBackend):
19     class PhpbbGroup(LazyGroup):
20         """
21         A group from phpbb.
22         """
23         pass
24
25     def __init__(self, request, auth):
26         super(LazyGroupsBackend, self).__init__(request)
27
28         self.auth = auth
29         self.request = request
30
31     def __iter__(self):
32         """
33         Return a list of group names.
34         """
35         return self.list_query("SELECT group_name \
36                                 FROM `%sgroups` \
37                                 WHERE group_single_user = 0"
38                                 % self.auth.phpbb_prefix)
39
40     def __contains__(self, group_name):
41         """
42         Does a group with the given name exist?
43         """
44         return self.single_query("SELECT EXISTS ( \
45                                       SELECT * \
46                                       FROM `%sgroups` \
47                                       WHERE group_single_user = 0 \
48                                             AND group_name=%%s)" % self.auth.phpbb_prefix,
49                                  group_name)
50
51     def __getitem__(self, group_name):
52         """
53         Get the group with the given name.
54         """
55         return self.PhpbbGroup(self.request, group_name, self)
56
57     def _iter_group_members(self, group_name):
58         """
59         Get all member names for the given group. This is called by
60         LazyGroup.__iter__.
61         """
62         return self.list_query ("SELECT username \
63                                  FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g  \
64                                  WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
65                                        AND ug.user_pending = 0 AND g.group_single_user = 0 \
66                                        AND g.group_name = %%s"
67                                  % (self.auth.phpbb_prefix, self.auth.phpbb_prefix, self.auth.phpbb_prefix),
68                                 group_name)
69
70     def _group_has_member(self, group_name, member):
71         """
72         Does the group with the given name have a member with the given name?
73         This is called by LazyGroup.__contains__.
74         """
75         return self.single_query ("SELECT EXISTS( \
76                                        SELECT * \
77                                        FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
78                                        WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
79                                              AND ug.user_pending = 0 AND g.group_single_user = 0 \
80                                              AND g.group_name = %%s AND u.username = %%s)"
81                                    % (self.auth.phpbb_prefix, self.auth.phpbb_prefix, self.auth.phpbb_prefix),
82                                   (group_name, member))
83         
84     def groups_with_member(self, member):
85         """
86         Return the group names for all groups that have a member with the
87         given name.
88         """
89         return self.list_query ("SELECT g.group_name \
90                                  FROM `%susers` as u, `%suser_group` as ug, `%sgroups` as g \
91                                  WHERE u.user_id = ug.user_id AND ug.group_id = g.group_id \
92                                        AND ug.user_pending = 0 AND g.group_single_user = 0 \
93                                        AND u.username = %%s"
94                                 % (self.auth.phpbb_prefix, self.auth.phpbb_prefix, self.auth.phpbb_prefix),
95                                 member)
96
97     def single_query(self, *args):
98         """
99         Runs an SQL query, that returns single row with a single column.
100         Returns just that single result.
101         """
102         conn = None
103         cursor = None
104         try:
105             conn = self.auth.connect(self.request)
106             cursor = conn.cursor()
107             cursor.execute(*args)
108
109             return cursor.fetchone()[0]
110         finally:
111             if cursor:
112                 cursor.close()
113             if conn:
114                 conn.close()
115         
116     def list_query(self, *args):
117         """
118         Runs an SQL query, that returns any number of single-column rows.
119         Returns the results as a list of single values
120         """
121         conn = None
122         cursor = None
123         try:
124             conn = self.auth.connect(self.request)
125             cursor = conn.cursor()
126             cursor.execute(*args)
127
128             for row in cursor:
129                 yield row[0]
130         finally:
131             if cursor:
132                 cursor.close()
133             if conn:
134                 conn.close()
135         
136 class PhpbbAuth(BaseAuth):
137     logout_possible = True
138     login_inputs    = ['username', 'password']
139     
140     def __init__(self, name='phpbb', dbhost=None, dbuser=None, dbpass=None, dbname=None, dbport=None, phpbb_prefix='', hint=None):
141         """
142             Authenticate using credentials from a phpbb database
143
144             The name parameter should be unique among all authentication methods.
145
146             The hint parameter is a snippet of HTML that is displayed below the login form.
147         """
148         self.dbhost  = dbhost
149         self.dbuser  = dbuser
150         self.dbpass  = dbpass
151         self.dbname  = dbname
152         self.dbport  = dbport
153         self.phpbb_prefix = phpbb_prefix
154         self.name    = name
155         self.hint    = hint
156
157         # Create a "constructor" to create a phpbb_groups instance, while
158         # passing ourselves to it.
159         self.groups_backend = lambda config, request: PhpbbGroupsBackend(request, self)
160     
161     def check_login(self, request, username, password):
162         """ Checks the given username password combination. Returns the
163         corresponding emailaddress, or False if authentication failed.
164         """
165         conn = self.connect(request)
166
167         if not conn:
168             return False
169
170         # Get some data. Note that we interpolate the prefix ourselves, since
171         # letting the mysql library do it only works with values (it adds ''
172         # automatically). Note also that this allows possible SQL injection
173         # through the phpbb_prefix variable, but that should be a trusted
174         # value anyway.
175         cursor = conn.cursor ()
176         cursor.execute ("SELECT user_password,user_email FROM `%susers` WHERE username=%%s" % self.phpbb_prefix, username)
177
178         # No data? No login.
179         if (cursor.rowcount == 0):
180             conn.close()
181             return False
182        
183         # Check password
184         row = cursor.fetchone()
185         conn.close()
186
187         if (md5.new(password).hexdigest() == row[0]):
188             return row[1]
189         else:
190             return False
191
192     def connect(self, request):
193         # This code was shamelessly stolen from
194         # django.db.backends.mysql.base.cursor
195         kwargs = {
196             'charset': 'utf8',
197             'use_unicode': False,
198         }
199         if self.dbuser:
200             kwargs['user'] = self.dbuser
201         if self.dbname:
202             kwargs['db'] = self.dbname
203         if self.dbpass:
204             kwargs['passwd'] = self.dbpass
205         if self.dbhost.startswith('/'):
206             kwargs['unix_socket'] = self.dbhost
207         elif self.dbhost:
208             kwargs['host'] = self.dbhost
209         if self.dbport:
210             kwargs['port'] = int(self.dbport)
211
212         # End stolen code
213
214         try:
215             conn = MySQLdb.connect (**kwargs)
216         except:
217             import sys
218             import traceback
219             info = sys.exc_info()
220             logging.error("phpbb_login: authentication failed due to exception connecting to DB, traceback follows...")
221             logging.error(''.join(traceback.format_exception(*info)))
222             return False
223
224         return conn
225
226     def login(self, request, user_obj, **kw):
227         """
228         Handle a login. Called by moinmoin.
229         """
230         try:
231             username = kw.get('username')
232             password = kw.get('password')
233
234             logging.debug("phpbb_login: Trying to log in, username=%r " % (username))
235            
236             # simply continue if something else already logged in
237             # successfully
238             if user_obj and user_obj.valid:
239                 return ContinueLogin(user_obj)
240
241             # Deny empty username or passwords
242             if not username or not password:
243                 return ContinueLogin(user_obj)
244
245             email = self.check_login(request, username, password)
246             
247             # Login incorrect
248             if (not email):
249                 logging.debug("phpbb_login: authentication failed for %s" % (username))
250                 return ContinueLogin(user_obj)
251
252             logging.debug("phpbb_login: authenticated %s (email %s)" % (username, email))
253
254             u = user.User(request, auth_username=username, auth_method=self.name, auth_attribs=('name', 'password', 'email'))
255             u.email = email
256             #u.remember_me = 0 # 0 enforces cookie_lifetime config param
257             u.create_or_update(True)
258
259             return ContinueLogin(u)
260         except:
261             import sys
262             import traceback
263             info = sys.exc_info()
264             logging.error("phpbb_login: authentication failed due to unexpected exception, traceback follows...")
265             logging.error(''.join(traceback.format_exception(*info)))
266             return ContinueLogin(user_obj)
267
268     def login_hint(self, request):
269         """ Return a snippet of HTML that is displayed with the login form. """
270         return self.hint
271
272 # vim: set sw=4 expandtab sts=4:vim