4 This module contains functions for registering new members, registering
5 members for terms, searching for members, and other member-related
8 Transactions are used in each method that modifies the database.
9 Future changes to the members database that need to be atomic
10 must also be moved into this module.
13 from csc.adm import terms
14 from csc.backends import ldapi
15 from csc.common import conf
16 from csc.common.excep import InvalidArgument
21 CONFIG_FILE = '/etc/csc/members.cf'
25 def load_configuration():
26 """Load Members Configuration"""
28 string_fields = [ 'studentid_regex', 'realname_regex', 'server',
29 'database', 'user', 'password', 'server_url', 'users_base',
30 'groups_base', 'admin_bind_dn', 'admin_bind_pw' ]
32 # read configuration file
33 cfg_tmp = conf.read(CONFIG_FILE)
35 # verify configuration
36 conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
38 # update the current configuration with the loaded values
45 ConfigurationException = conf.ConfigurationException
47 class MemberException(Exception):
48 """Base exception class for member-related errors."""
50 class DuplicateStudentID(MemberException):
51 """Exception class for student ID conflicts."""
52 def __init__(self, studentid):
53 self.studentid = studentid
55 return "Student ID already exists in the database: %s" % self.studentid
57 class InvalidStudentID(MemberException):
58 """Exception class for malformed student IDs."""
59 def __init__(self, studentid):
60 self.studentid = studentid
62 return "Student ID is invalid: %s" % self.studentid
64 class InvalidTerm(MemberException):
65 """Exception class for malformed terms."""
66 def __init__(self, term):
69 return "Term is invalid: %s" % self.term
71 class InvalidRealName(MemberException):
72 """Exception class for invalid real names."""
73 def __init__(self, name):
76 return "Name is invalid: %s" % self.name
78 class NoSuchMember(MemberException):
79 """Exception class for nonexistent members."""
80 def __init__(self, memberid):
81 self.memberid = memberid
83 return "Member not found: %d" % self.memberid
87 ### Connection Management ###
89 # global directory connection
90 ldap_connection = ldapi.LDAPConnection()
93 """Connect to PostgreSQL."""
96 ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base'])
100 """Disconnect from PostgreSQL."""
102 ldap_connection.disconnect()
106 """Determine whether the connection has been established."""
108 return ldap_connection.connected()
114 def new(uid, realname, studentid=None, program=None):
116 Registers a new CSC member. The member is added to the members table
117 and registered for the current term.
120 uid - the initial user id
121 realname - the full real name of the member
122 studentid - the student id number of the member
123 program - the program of study of the member
125 Returns: the username of the new member
128 DuplicateStudentID - if the student id already exists in the database
129 InvalidStudentID - if the student id is malformed
130 InvalidRealName - if the real name is malformed
132 Example: new("Michael Spang", program="CS") -> "mspang"
135 # blank attributes should be NULL
136 if studentid == '': studentid = None
137 if program == '': program = None
138 if uid == '': uid = None
140 # check the student id format
141 if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
142 raise InvalidStudentID(studentid)
144 # check real name format (UNIX account real names must not contain [,:=])
145 if not re.match(cfg['realname_regex'], realname):
146 raise InvalidRealName(realname)
148 # check for duplicate student id
149 member = ldap_connection.member_search_studentid(studentid)
151 raise DuplicateStudentID(studentid)
153 # check for duplicate userid
154 member = ldap_connection.user_lookup(uid)
156 raise InvalidArgument("uid", uid, "duplicate uid")
158 # add the member to the directory
159 ldap_connection.member_add(uid, realname, studentid, program)
161 # register them for this term in the directory
162 member = ldap_connection.member_lookup(uid)
163 member['term'] = [ terms.current() ]
164 ldap_connection.user_modify(uid, member)
171 Look up attributes of a member by userid.
173 Returns: a dictionary of attributes
175 Example: get('mspang') -> {
176 'cn': [ 'Michael Spang' ],
177 'program': [ 'Computer Science' ],
182 return ldap_connection.user_lookup(userid)
185 def get_studentid(studentid):
187 Look up attributes of a member by studentid.
190 studentid - the student ID number
192 Returns: a dict of members
194 Example: get(...) -> {
196 'name': [ 'Michael Spang' ],
197 'program': [ 'Computer Science' ],
203 return ldap_connection.member_search_studentid(studentid)
208 Build a list of members in a term.
211 term - the term to match members against
213 Returns: a list of members
215 Example: list_term('f2006'): -> {
216 'mspang': { 'cn': 'Michael Spang', ... },
217 'ctdalek': { 'cn': 'Calum T. Dalek', ... },
222 return ldap_connection.member_search_term(term)
227 Build a list of members with matching names.
230 name - the name to match members against
232 Returns: a list of member dictionaries
234 Example: list_name('Spang'): -> {
235 'mspang': { 'cn': 'Michael Spang', ... },
240 return ldap_connection.member_search_name(name)
245 Erase all records of a member.
247 Note: real members are never removed from the database
249 Returns: ldap entry of the member
252 NoSuchMember - if the user id does not exist
254 Example: delete('ctdalek') -> { 'cn': [ 'Calum T. Dalek' ], 'term': ['s1993'], ... }
258 member = ldap_connection.user_lookup(userid)
262 raise NoSuchMember(userid)
264 # remove data from the directory
265 uid = member['uid'][0]
266 ldap_connection.user_delete(uid)
274 def register(userid, term_list):
276 Registers a member for one or more terms.
279 userid - the member's username
280 term_list - the term to register for, or a list of terms
283 InvalidTerm - if a term is malformed
285 Example: register(3349, "w2007")
287 Example: register(3349, ["w2007", "s2007"])
290 if type(term_list) in (str, unicode):
291 term_list = [ term_list ]
293 ldap_member = ldap_connection.member_lookup(userid)
294 if ldap_member and 'term' not in ldap_member:
295 ldap_member['term'] = []
298 raise NoSuchMember(userid)
300 for term in term_list:
303 if not re.match('^[wsf][0-9]{4}$', term):
304 raise InvalidTerm(term)
306 # add the term to the directory
307 ldap_member['term'].append(term)
309 ldap_connection.user_modify(userid, ldap_member)
312 def registered(userid, term):
314 Determines whether a member is registered
318 userid - the member's username
319 term - the term to check
321 Returns: whether the member is registered
323 Example: registered("mspang", "f2006") -> True
326 member = ldap_connection.member_lookup(userid)
327 return 'term' in member and term in member['term']
330 def member_terms(userid):
332 Retrieves a list of terms a member is
336 userid - the member's username
338 Returns: list of term strings
340 Example: registered('ctdalek') -> 's1993'
343 member = ldap_connection.member_lookup(userid)
344 if not 'term' in member:
347 return member['term']
353 if __name__ == '__main__':
355 from csc.common.test import *
357 # t=test m=member s=student u=updated
358 tmname = 'Test Member'
360 tmprogram = 'Metaphysics'
362 tm2name = 'Test Member 2'
363 tm2uid = 'testmember2'
365 tm2uname = 'Test Member II'
367 tm2uprogram = 'Pseudoscience'
369 tmdict = {'cn': [tmname], 'uid': [tmuid], 'program': [tmprogram], 'studentid': [tmsid] }
370 tm2dict = {'cn': [tm2name], 'uid': [tm2uid], 'studentid': [tm2sid] }
371 tm2udict = {'cn': [tm2uname], 'uid': [tm2uid], 'program': [tm2uprogram], 'studentid': [tm2usid] }
373 thisterm = terms.current()
374 nextterm = terms.next(thisterm)
381 assert_equal(True, connected())
384 dmid = get_studentid(tmsid)
385 if tmuid in dmid: delete(dmid[tmuid]['uid'][0])
386 dmid = get_studentid(tm2sid)
387 if tm2uid in dmid: delete(dmid[tm2uid]['uid'][0])
388 dmid = get_studentid(tm2usid)
389 if tm2uid in dmid: delete(dmid[tm2uid]['uid'][0])
392 tmid = new(tmuid, tmname, tmsid, tmprogram)
393 tm2id = new(tm2uid, tm2name, tm2sid)
397 assert_equal(True, registered(tmid, thisterm))
398 assert_equal(True, registered(tm2id, thisterm))
399 assert_equal(False, registered(tmid, nextterm))
404 del tmp['objectClass']
406 assert_equal(tmdict, tmp)
408 del tmp['objectClass']
410 assert_equal(tm2dict, tmp)
414 assert_equal(True, tmid in list_name(tmname).keys())
415 assert_equal(True, tm2id in list_name(tm2name).keys())
419 register(tmid, nextterm)
420 assert_equal(True, registered(tmid, nextterm))
424 assert_equal([thisterm, nextterm], member_terms(tmid))
425 assert_equal([thisterm], member_terms(tm2id))
429 assert_equal(True, tmid in list_term(thisterm).keys())
430 assert_equal(True, tmid in list_term(nextterm).keys())
431 assert_equal(True, tm2id in list_term(thisterm).keys())
432 assert_equal(False, tm2id in list_term(nextterm).keys())
437 del tmp['objectClass']
439 assert_equal(tm2dict, tmp)
443 tmp = get_studentid(tm2sid)[tm2uid]
444 del tmp['objectClass']
446 assert_equal(tm2dict, tmp)
447 tmp = get_studentid(tmsid)[tmuid]
448 del tmp['objectClass']
450 assert_equal(tmdict, tmp)
460 assert_equal(False, connected())