409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
1 |
# IVLE - Informatics Virtual Learning Environment
|
2 |
# Copyright (C) 2007-2008 The University of Melbourne
|
|
3 |
#
|
|
4 |
# This program is free software; you can redistribute it and/or modify
|
|
5 |
# it under the terms of the GNU General Public License as published by
|
|
6 |
# the Free Software Foundation; either version 2 of the License, or
|
|
7 |
# (at your option) any later version.
|
|
8 |
#
|
|
9 |
# This program is distributed in the hope that it will be useful,
|
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12 |
# GNU General Public License for more details.
|
|
13 |
#
|
|
14 |
# You should have received a copy of the GNU General Public License
|
|
15 |
# along with this program; if not, write to the Free Software
|
|
16 |
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17 |
||
18 |
# Module: Database
|
|
19 |
# Author: Matt Giuca
|
|
468
by mattgiuca
db.py: Epic Refactor. |
20 |
# Date: 15/2/2008
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
21 |
|
22 |
# Code to talk to the PostgreSQL database.
|
|
23 |
# (This is the Data Access Layer).
|
|
24 |
# All DB code should be in this module to ensure portability if we want to
|
|
25 |
# change the DB implementation.
|
|
26 |
# This means no SQL strings should be outside of this module. Add functions
|
|
27 |
# here to perform the activities needed, and place the SQL code for those
|
|
28 |
# activities within.
|
|
29 |
||
30 |
# CAUTION to editors of this module.
|
|
31 |
# All string inputs must be sanitized by calling _escape before being
|
|
32 |
# formatted into an SQL query string.
|
|
33 |
||
34 |
import pg |
|
35 |
import conf |
|
36 |
import md5 |
|
468
by mattgiuca
db.py: Epic Refactor. |
37 |
import copy |
669
by mattgiuca
Timestamps are now stored within the program as Python "time" module's |
38 |
import time |
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
39 |
|
500
by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries. |
40 |
from common import (caps, user) |
476
by mattgiuca
Added new module: common/caps.py. This is the Capabilities centre of IVLE. |
41 |
|
669
by mattgiuca
Timestamps are now stored within the program as Python "time" module's |
42 |
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S' |
43 |
||
468
by mattgiuca
db.py: Epic Refactor. |
44 |
def _escape(val): |
45 |
"""Wrapper around pg.escape_string. Prepares the Python value for use in
|
|
46 |
SQL. Returns a string, which may be safely placed verbatim into an SQL
|
|
47 |
query.
|
|
48 |
Handles the following types:
|
|
49 |
* str: Escapes the string, and also quotes it.
|
|
50 |
* int/long/float: Just converts to an unquoted string.
|
|
51 |
* bool: Returns as "TRUE" or "FALSE", unquoted.
|
|
52 |
* NoneType: Returns "NULL", unquoted.
|
|
476
by mattgiuca
Added new module: common/caps.py. This is the Capabilities centre of IVLE. |
53 |
* common.caps.Role: Returns the role as a quoted, lowercase string.
|
468
by mattgiuca
db.py: Epic Refactor. |
54 |
Raises a DBException if val has an unsupported type.
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
55 |
"""
|
56 |
# "E'" is postgres's way of making "escape" strings.
|
|
57 |
# Such strings allow backslashes to escape things. Since escape_string
|
|
58 |
# converts a single backslash into two backslashes, it needs to be fed
|
|
59 |
# into E mode.
|
|
60 |
# Ref: http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html
|
|
61 |
# WARNING: PostgreSQL-specific code
|
|
468
by mattgiuca
db.py: Epic Refactor. |
62 |
if val is None: |
429
by mattgiuca
makeuser and common.db now allow StudentID to be unsupplied / None. |
63 |
return "NULL" |
468
by mattgiuca
db.py: Epic Refactor. |
64 |
elif isinstance(val, str): |
65 |
return "E'" + pg.escape_string(val) + "'" |
|
66 |
elif isinstance(val, bool): |
|
67 |
return "TRUE" if val else "FALSE" |
|
68 |
elif isinstance(val, int) or isinstance(val, long) \ |
|
69 |
or isinstance(val, float): |
|
70 |
return str(val) |
|
476
by mattgiuca
Added new module: common/caps.py. This is the Capabilities centre of IVLE. |
71 |
elif isinstance(val, caps.Role): |
72 |
return _escape(str(val)) |
|
669
by mattgiuca
Timestamps are now stored within the program as Python "time" module's |
73 |
elif isinstance(val, time.struct_time): |
74 |
return _escape(time.strftime(TIMESTAMP_FORMAT, val)) |
|
468
by mattgiuca
db.py: Epic Refactor. |
75 |
else: |
76 |
raise DBException("Attempt to insert an unsupported type " |
|
77 |
"into the database") |
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
78 |
|
79 |
def _passhash(password): |
|
80 |
return md5.md5(password).hexdigest() |
|
81 |
||
82 |
class DBException(Exception): |
|
83 |
"""A DBException is for bad conditions in the database or bad input to
|
|
84 |
these methods. If Postgres throws an exception it does not get rebadged.
|
|
85 |
This is only for additional exceptions."""
|
|
86 |
pass
|
|
87 |
||
88 |
class DB: |
|
89 |
"""An IVLE database object. This object provides an interface to
|
|
90 |
interacting with the IVLE database without using any external SQL.
|
|
91 |
||
92 |
Most methods of this class have an optional dry argument. If true, they
|
|
93 |
will return the SQL query string and NOT actually execute it. (For
|
|
94 |
debugging purposes).
|
|
95 |
||
96 |
Methods may throw db.DBException, or any of the pg exceptions as well.
|
|
97 |
(In general, be prepared to catch exceptions!)
|
|
98 |
"""
|
|
99 |
def __init__(self): |
|
100 |
"""Connects to the database and creates a DB object.
|
|
101 |
Takes no parameters - gets all the DB info from the configuration."""
|
|
484
by mattgiuca
lib/common/db.py: Fixed a cascading error if the constructor throws an |
102 |
self.open = False |
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
103 |
self.db = pg.connect(dbname=conf.db_dbname, host=conf.db_host, |
104 |
port=conf.db_port, user=conf.db_user, passwd=conf.db_password) |
|
466
by drtomc
db: Make the DB object self-closing. |
105 |
self.open = True |
106 |
||
107 |
def __del__(self): |
|
108 |
if self.open: |
|
109 |
self.db.close() |
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
110 |
|
468
by mattgiuca
db.py: Epic Refactor. |
111 |
# GENERIC DB FUNCTIONS #
|
112 |
||
113 |
@staticmethod
|
|
114 |
def check_dict(dict, tablefields, disallowed=frozenset([]), must=False): |
|
115 |
"""Checks that a dict does not contain keys that are not fields
|
|
116 |
of the specified table.
|
|
117 |
dict: A mapping from string keys to values; the keys are checked to
|
|
118 |
see that they correspond to login table fields.
|
|
119 |
tablefields: Collection of strings for field names in the table.
|
|
120 |
Only these fields will be allowed.
|
|
121 |
disallowed: Optional collection of strings for field names that are
|
|
122 |
not allowed.
|
|
123 |
must: If True, the dict MUST contain all fields in tablefields.
|
|
124 |
If False, it may contain any subset of the fields.
|
|
125 |
Returns True if the dict is valid, False otherwise.
|
|
126 |
"""
|
|
127 |
allowed = frozenset(tablefields) - frozenset(disallowed) |
|
128 |
dictkeys = frozenset(dict.keys()) |
|
129 |
if must: |
|
130 |
return allowed == dictkeys |
|
131 |
else: |
|
132 |
return allowed.issuperset(dictkeys) |
|
133 |
||
134 |
def insert(self, dict, tablename, tablefields, disallowed=frozenset([]), |
|
135 |
dry=False): |
|
136 |
"""Inserts a new row in a table, using data from a supplied
|
|
137 |
dictionary (which will be checked by check_dict).
|
|
138 |
dict: Dictionary mapping column names to values. The values may be
|
|
139 |
any of the following types:
|
|
140 |
str, int, long, float, NoneType.
|
|
141 |
tablename: String, name of the table to insert into. Will NOT be
|
|
142 |
escaped - must be a valid identifier.
|
|
143 |
tablefields, disallowed: see check_dict.
|
|
144 |
dry: Returns the SQL query as a string, and does not execute it.
|
|
145 |
Raises a DBException if the dictionary contains invalid fields.
|
|
146 |
"""
|
|
147 |
if not DB.check_dict(dict, tablefields, disallowed): |
|
585
by drtomc
db: remove local_password before trying to create accounts. |
148 |
extras = set(dict.keys()) - tablefields |
149 |
raise DBException("Supplied dictionary contains invalid fields. (%s)" % (repr(extras))) |
|
468
by mattgiuca
db.py: Epic Refactor. |
150 |
# Build two lists concurrently: field names and values, as SQL strings
|
151 |
fieldnames = [] |
|
152 |
values = [] |
|
153 |
for k,v in dict.items(): |
|
154 |
fieldnames.append(k) |
|
155 |
values.append(_escape(v)) |
|
156 |
if len(fieldnames) == 0: return |
|
157 |
fieldnames = ', '.join(fieldnames) |
|
158 |
values = ', '.join(values) |
|
159 |
query = ("INSERT INTO %s (%s) VALUES (%s);" |
|
160 |
% (tablename, fieldnames, values)) |
|
161 |
if dry: return query |
|
162 |
self.db.query(query) |
|
163 |
||
164 |
def update(self, primarydict, updatedict, tablename, tablefields, |
|
165 |
primary_keys, disallowed_update=frozenset([]), dry=False): |
|
166 |
"""Updates a row in a table, matching against primarydict to find the
|
|
167 |
row, and using the data in updatedict (which will be checked by
|
|
168 |
check_dict).
|
|
169 |
primarydict: Dict mapping column names to values. The keys should be
|
|
170 |
the table's primary key. Only rows which match this dict's values
|
|
171 |
will be updated.
|
|
172 |
updatedict: Dict mapping column names to values. The columns will be
|
|
173 |
updated with the given values for the matched rows.
|
|
174 |
tablename, tablefields, disallowed_update: See insert.
|
|
175 |
primary_keys: Collection of strings which together form the primary
|
|
176 |
key for this table. primarydict must contain all of these as keys,
|
|
177 |
and only these keys.
|
|
178 |
"""
|
|
179 |
if (not (DB.check_dict(primarydict, primary_keys, must=True) |
|
180 |
and DB.check_dict(updatedict, tablefields, disallowed_update))): |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
181 |
raise DBException("Supplied dictionary contains invalid or missing fields (1).") |
468
by mattgiuca
db.py: Epic Refactor. |
182 |
# Make a list of SQL fragments of the form "field = 'new value'"
|
183 |
# These fragments are ALREADY-ESCAPED
|
|
184 |
setlist = [] |
|
185 |
for k,v in updatedict.items(): |
|
186 |
setlist.append("%s = %s" % (k, _escape(v))) |
|
187 |
wherelist = [] |
|
188 |
for k,v in primarydict.items(): |
|
189 |
wherelist.append("%s = %s" % (k, _escape(v))) |
|
190 |
if len(setlist) == 0 or len(wherelist) == 0: |
|
191 |
return
|
|
192 |
# Join the fragments into a comma-separated string
|
|
193 |
setstring = ', '.join(setlist) |
|
194 |
wherestring = ' AND '.join(wherelist) |
|
195 |
# Build the whole query as an UPDATE statement
|
|
196 |
query = ("UPDATE %s SET %s WHERE %s;" |
|
197 |
% (tablename, setstring, wherestring)) |
|
198 |
if dry: return query |
|
199 |
self.db.query(query) |
|
200 |
||
201 |
def delete(self, primarydict, tablename, primary_keys, dry=False): |
|
202 |
"""Deletes a row in the table, matching against primarydict to find
|
|
203 |
the row.
|
|
204 |
primarydict, tablename, primary_keys: See update.
|
|
205 |
"""
|
|
206 |
if not DB.check_dict(primarydict, primary_keys, must=True): |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
207 |
raise DBException("Supplied dictionary contains invalid or missing fields (2).") |
468
by mattgiuca
db.py: Epic Refactor. |
208 |
wherelist = [] |
209 |
for k,v in primarydict.items(): |
|
210 |
wherelist.append("%s = %s" % (k, _escape(v))) |
|
211 |
if len(wherelist) == 0: |
|
212 |
return
|
|
213 |
wherestring = ' AND '.join(wherelist) |
|
214 |
query = ("DELETE FROM %s WHERE %s;" % (tablename, wherestring)) |
|
215 |
if dry: return query |
|
216 |
self.db.query(query) |
|
217 |
||
218 |
def get_single(self, primarydict, tablename, getfields, primary_keys, |
|
219 |
error_notfound="No rows found", dry=False): |
|
220 |
"""Retrieves a single row from a table, returning it as a dictionary
|
|
221 |
mapping field names to values. Matches against primarydict to find the
|
|
222 |
row.
|
|
223 |
primarydict, tablename, primary_keys: See update/delete.
|
|
224 |
getfields: Collection of strings; the field names which will be
|
|
225 |
returned as keys in the dictionary.
|
|
226 |
error_notfound: Error message if 0 rows match.
|
|
227 |
Raises a DBException if 0 rows match, with error_notfound as the msg.
|
|
228 |
Raises an AssertError if >1 rows match (this should not happen if
|
|
229 |
primary_keys is indeed the primary key).
|
|
230 |
"""
|
|
231 |
if not DB.check_dict(primarydict, primary_keys, must=True): |
|
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
232 |
raise DBException("Supplied dictionary contains invalid or missing fields (3).") |
468
by mattgiuca
db.py: Epic Refactor. |
233 |
wherelist = [] |
234 |
for k,v in primarydict.items(): |
|
235 |
wherelist.append("%s = %s" % (k, _escape(v))) |
|
236 |
if len(getfields) == 0 or len(wherelist) == 0: |
|
237 |
return
|
|
238 |
# Join the fragments into a comma-separated string
|
|
239 |
getstring = ', '.join(getfields) |
|
240 |
wherestring = ' AND '.join(wherelist) |
|
241 |
# Build the whole query as an SELECT statement
|
|
242 |
query = ("SELECT %s FROM %s WHERE %s;" |
|
243 |
% (getstring, tablename, wherestring)) |
|
244 |
if dry: return query |
|
245 |
result = self.db.query(query) |
|
246 |
# Expecting exactly one
|
|
247 |
if result.ntuples() != 1: |
|
248 |
# It should not be possible for ntuples to be greater than 1
|
|
249 |
assert (result.ntuples() < 1) |
|
250 |
raise DBException(error_notfound) |
|
251 |
# Return as a dictionary
|
|
252 |
return result.dictresult()[0] |
|
253 |
||
254 |
def get_all(self, tablename, getfields, dry=False): |
|
255 |
"""Retrieves all rows from a table, returning it as a list of
|
|
256 |
dictionaries mapping field names to values.
|
|
257 |
tablename, getfields: See get_single.
|
|
258 |
"""
|
|
259 |
if len(getfields) == 0: |
|
260 |
return
|
|
261 |
getstring = ', '.join(getfields) |
|
262 |
query = ("SELECT %s FROM %s;" % (getstring, tablename)) |
|
263 |
if dry: return query |
|
264 |
return self.db.query(query).dictresult() |
|
265 |
||
492
by mattgiuca
db.py: Added start_transaction, commit and rollback methods (tested). |
266 |
def start_transaction(self, dry=False): |
267 |
"""Starts a DB transaction.
|
|
268 |
Will not commit any changes until self.commit() is called.
|
|
269 |
"""
|
|
270 |
query = "START TRANSACTION;" |
|
271 |
if dry: return query |
|
272 |
self.db.query(query) |
|
273 |
||
274 |
def commit(self, dry=False): |
|
275 |
"""Commits (ends) a DB transaction.
|
|
276 |
Commits all changes since the call to start_transaction.
|
|
277 |
"""
|
|
278 |
query = "COMMIT;" |
|
279 |
if dry: return query |
|
280 |
self.db.query(query) |
|
281 |
||
282 |
def rollback(self, dry=False): |
|
283 |
"""Rolls back (ends) a DB transaction, undoing all changes since the
|
|
284 |
call to start_transaction.
|
|
285 |
"""
|
|
286 |
query = "ROLLBACK;" |
|
287 |
if dry: return query |
|
288 |
self.db.query(query) |
|
289 |
||
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
290 |
# USER MANAGEMENT FUNCTIONS #
|
291 |
||
468
by mattgiuca
db.py: Epic Refactor. |
292 |
login_primary = frozenset(["login"]) |
470
by mattgiuca
db: Added a field login_fields_list which is an ordered version of the |
293 |
login_fields_list = [ |
468
by mattgiuca
db.py: Epic Refactor. |
294 |
"login", "passhash", "state", "unixid", "email", "nick", "fullname", |
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
295 |
"rolenm", "studentid", "acct_exp", "pass_exp", "last_login", "svn_pass" |
470
by mattgiuca
db: Added a field login_fields_list which is an ordered version of the |
296 |
]
|
297 |
login_fields = frozenset(login_fields_list) |
|
468
by mattgiuca
db.py: Epic Refactor. |
298 |
|
532
by mattgiuca
db.py: Augmented create_user. |
299 |
def create_user(self, user_obj=None, dry=False, **kwargs): |
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
300 |
"""Creates a user login entry in the database.
|
532
by mattgiuca
db.py: Augmented create_user. |
301 |
Two ways to call this - passing a user object, or passing
|
302 |
all fields as separate arguments.
|
|
303 |
||
304 |
Either pass a "user_obj" as the first argument (in which case other
|
|
305 |
fields will be ignored), or pass all fields as arguments.
|
|
306 |
||
469
by mattgiuca
db.py: Changed interface (again) to user management methods: Changed the dict |
307 |
All user fields are to be passed as args. The argument names
|
468
by mattgiuca
db.py: Epic Refactor. |
308 |
are the field names of the "login" table of the DB schema.
|
309 |
However, instead of supplying a "passhash", you must supply a
|
|
469
by mattgiuca
db.py: Changed interface (again) to user management methods: Changed the dict |
310 |
"password" argument, which will be hashed internally.
|
468
by mattgiuca
db.py: Epic Refactor. |
311 |
Also "state" must not given explicitly; it is implicitly set to
|
451
by mattgiuca
Updated common/db.py and listusers to deal with the new "state" column in the |
312 |
"no_agreement".
|
468
by mattgiuca
db.py: Epic Refactor. |
313 |
Raises an exception if the user already exists, or the dict contains
|
314 |
invalid keys or is missing required keys.
|
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
315 |
"""
|
469
by mattgiuca
db.py: Changed interface (again) to user management methods: Changed the dict |
316 |
if 'passhash' in kwargs: |
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
317 |
raise DBException("Supplied arguments include passhash (invalid) (1).") |
468
by mattgiuca
db.py: Epic Refactor. |
318 |
# Make a copy of the dict. Change password to passhash (hashing it),
|
319 |
# and set 'state' to "no_agreement".
|
|
532
by mattgiuca
db.py: Augmented create_user. |
320 |
if user_obj is None: |
321 |
# Use the kwargs
|
|
322 |
fields = copy.copy(kwargs) |
|
323 |
else: |
|
324 |
# Use the user object
|
|
325 |
fields = dict(user_obj) |
|
326 |
if 'password' in fields: |
|
327 |
fields['passhash'] = _passhash(fields['password']) |
|
328 |
del fields['password'] |
|
329 |
if 'role' in fields: |
|
330 |
# Convert role to rolenm
|
|
331 |
fields['rolenm'] = str(user_obj.role) |
|
332 |
del fields['role'] |
|
333 |
if user_obj is None: |
|
334 |
fields['state'] = "no_agreement" |
|
335 |
# else, we'll trust the user, but it SHOULD be "no_agreement"
|
|
336 |
# (We can't change it because then the user object would not
|
|
337 |
# reflect the DB).
|
|
585
by drtomc
db: remove local_password before trying to create accounts. |
338 |
if 'local_password' in fields: |
339 |
del fields['local_password'] |
|
468
by mattgiuca
db.py: Epic Refactor. |
340 |
# Execute the query.
|
532
by mattgiuca
db.py: Augmented create_user. |
341 |
return self.insert(fields, "login", self.login_fields, dry=dry) |
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
342 |
|
469
by mattgiuca
db.py: Changed interface (again) to user management methods: Changed the dict |
343 |
def update_user(self, login, dry=False, **kwargs): |
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
344 |
"""Updates fields of a particular user. login is the name of the user
|
468
by mattgiuca
db.py: Epic Refactor. |
345 |
to update. The dict contains the fields which will be modified, and
|
346 |
their new values. If any value is omitted from the dict, it does not
|
|
347 |
get modified. login and studentid may not be modified.
|
|
348 |
Passhash may be modified by supplying a "password" field, in
|
|
349 |
cleartext, not a hashed password.
|
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
350 |
|
351 |
Note that no checking is done. It is expected this function is called
|
|
352 |
by a trusted source. In particular, it allows the password to be
|
|
353 |
changed without knowing the old password. The caller should check
|
|
354 |
that the user knows the existing password before calling this function
|
|
355 |
with a new one.
|
|
356 |
"""
|
|
469
by mattgiuca
db.py: Changed interface (again) to user management methods: Changed the dict |
357 |
if 'passhash' in kwargs: |
522
by drtomc
Add quite a lot of stuff to get usrmgt happening. |
358 |
raise DBException("Supplied arguments include passhash (invalid) (2).") |
469
by mattgiuca
db.py: Changed interface (again) to user management methods: Changed the dict |
359 |
if "password" in kwargs: |
360 |
kwargs = copy.copy(kwargs) |
|
361 |
kwargs['passhash'] = _passhash(kwargs['password']) |
|
362 |
del kwargs['password'] |
|
363 |
return self.update({"login": login}, kwargs, "login", |
|
364 |
self.login_fields, self.login_primary, ["login", "studentid"], |
|
365 |
dry=dry) |
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
366 |
|
367 |
def get_user(self, login, dry=False): |
|
500
by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries. |
368 |
"""Given a login, returns a User object containing details looked up
|
369 |
in the DB.
|
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
370 |
|
371 |
Raises a DBException if the login is not found in the DB.
|
|
372 |
"""
|
|
500
by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries. |
373 |
userdict = self.get_single({"login": login}, "login", |
575
by mattgiuca
common/usr, common/db: Added field to User, "local_password", |
374 |
self.login_fields, self.login_primary, |
468
by mattgiuca
db.py: Epic Refactor. |
375 |
error_notfound="get_user: No user with that login name", dry=dry) |
500
by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries. |
376 |
if dry: |
377 |
return userdict # Query string |
|
378 |
# Package into a User object
|
|
379 |
return user.User(**userdict) |
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
380 |
|
428
by mattgiuca
common.db: Added get_users function to get all users. |
381 |
def get_users(self, dry=False): |
500
by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries. |
382 |
"""Returns a list of all users in the DB, as User objects.
|
428
by mattgiuca
common.db: Added get_users function to get all users. |
383 |
"""
|
575
by mattgiuca
common/usr, common/db: Added field to User, "local_password", |
384 |
userdicts = self.get_all("login", self.login_fields, dry=dry) |
500
by mattgiuca
db: get_user and get_users now return User objects instead of dictionaries. |
385 |
if dry: |
386 |
return userdicts # Query string |
|
387 |
# Package into User objects
|
|
388 |
return [user.User(**userdict) for userdict in userdicts] |
|
428
by mattgiuca
common.db: Added get_users function to get all users. |
389 |
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
390 |
def user_authenticate(self, login, password, dry=False): |
391 |
"""Performs a password authentication on a user. Returns True if
|
|
466
by drtomc
db: Make the DB object self-closing. |
392 |
"passhash" is the correct passhash for the given login, False
|
509
by mattgiuca
common.db: Rewrote user_authenticate to return 3 values (True, false, None) |
393 |
if the passhash does not match the password in the DB,
|
394 |
and None if the passhash in the DB is NULL.
|
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
395 |
Also returns False if the login does not exist (so if you want to
|
396 |
differentiate these cases, use get_user and catch an exception).
|
|
397 |
"""
|
|
509
by mattgiuca
common.db: Rewrote user_authenticate to return 3 values (True, false, None) |
398 |
query = "SELECT passhash FROM login WHERE login = '%s';" % login |
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
399 |
if dry: return query |
400 |
result = self.db.query(query) |
|
509
by mattgiuca
common.db: Rewrote user_authenticate to return 3 values (True, false, None) |
401 |
if result.ntuples() == 1: |
402 |
# Valid username. Check password.
|
|
403 |
passhash = result.getresult()[0][0] |
|
404 |
if passhash is None: |
|
405 |
return None |
|
406 |
return _passhash(password) == passhash |
|
407 |
else: |
|
408 |
return False |
|
409
by mattgiuca
Moved www/conf and www/common to a new directory lib. This separates the "web" |
409 |
|
410 |
def close(self): |
|
411 |
"""Close the DB connection. Do not call any other functions after
|
|
412 |
this. (The behaviour of doing so is undefined).
|
|
413 |
"""
|
|
414 |
self.db.close() |
|
466
by drtomc
db: Make the DB object self-closing. |
415 |
self.open = False |