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