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 = [ '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 InvalidTerm(MemberException):
51 """Exception class for malformed terms."""
52 def __init__(self, term):
55 return "Term is invalid: %s" % self.term
57 class InvalidRealName(MemberException):
58 """Exception class for invalid real names."""
59 def __init__(self, name):
62 return "Name is invalid: %s" % self.name
64 class NoSuchMember(MemberException):
65 """Exception class for nonexistent members."""
66 def __init__(self, memberid):
67 self.memberid = memberid
69 return "Member not found: %d" % self.memberid
73 ### Connection Management ###
75 # global directory connection
76 ldap_connection = ldapi.LDAPConnection()
79 """Connect to LDAP."""
82 ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base'])
86 """Disconnect from LDAP."""
88 ldap_connection.disconnect()
92 """Determine whether the connection has been established."""
94 return ldap_connection.connected()
100 def new(uid, realname, program=None):
102 Registers a new CSC member. The member is added to the members table
103 and registered for the current term.
106 uid - the initial user id
107 realname - the full real name of the member
108 program - the program of study of the member
110 Returns: the username of the new member
113 InvalidRealName - if the real name is malformed
115 Example: new("Michael Spang", program="CS") -> "mspang"
118 # blank attributes should be NULL
119 if program == '': program = None
120 if uid == '': uid = None
123 # check real name format (UNIX account real names must not contain [,:=])
124 if not re.match(cfg['realname_regex'], realname):
125 raise InvalidRealName(realname)
127 # check for duplicate userid
128 member = ldap_connection.user_lookup(uid)
130 raise InvalidArgument("uid", uid, "duplicate uid")
132 # add the member to the directory
133 ldap_connection.member_add(uid, realname, program)
135 # register them for this term in the directory
136 member = ldap_connection.member_lookup(uid)
137 member['term'] = [ terms.current() ]
138 ldap_connection.user_modify(uid, member)
145 Look up attributes of a member by userid.
147 Returns: a dictionary of attributes
149 Example: get('mspang') -> {
150 'cn': [ 'Michael Spang' ],
151 'program': [ 'Computer Science' ],
156 return ldap_connection.user_lookup(userid)
161 Build a list of members in a term.
164 term - the term to match members against
166 Returns: a list of members
168 Example: list_term('f2006'): -> {
169 'mspang': { 'cn': 'Michael Spang', ... },
170 'ctdalek': { 'cn': 'Calum T. Dalek', ... },
175 return ldap_connection.member_search_term(term)
180 Build a list of members with matching names.
183 name - the name to match members against
185 Returns: a list of member dictionaries
187 Example: list_name('Spang'): -> {
188 'mspang': { 'cn': 'Michael Spang', ... },
193 return ldap_connection.member_search_name(name)
198 Erase all records of a member.
200 Note: real members are never removed from the database
202 Returns: ldap entry of the member
205 NoSuchMember - if the user id does not exist
207 Example: delete('ctdalek') -> { 'cn': [ 'Calum T. Dalek' ], 'term': ['s1993'], ... }
211 member = ldap_connection.user_lookup(userid)
215 raise NoSuchMember(userid)
217 # remove data from the directory
218 uid = member['uid'][0]
219 ldap_connection.user_delete(uid)
227 def register(userid, term_list):
229 Registers a member for one or more terms.
232 userid - the member's username
233 term_list - the term to register for, or a list of terms
236 InvalidTerm - if a term is malformed
238 Example: register(3349, "w2007")
240 Example: register(3349, ["w2007", "s2007"])
243 if type(term_list) in (str, unicode):
244 term_list = [ term_list ]
246 ldap_member = ldap_connection.member_lookup(userid)
247 if ldap_member and 'term' not in ldap_member:
248 ldap_member['term'] = []
251 raise NoSuchMember(userid)
253 for term in term_list:
256 if not re.match('^[wsf][0-9]{4}$', term):
257 raise InvalidTerm(term)
259 # add the term to the directory
260 ldap_member['term'].append(term)
262 ldap_connection.user_modify(userid, ldap_member)
265 def registered(userid, term):
267 Determines whether a member is registered
271 userid - the member's username
272 term - the term to check
274 Returns: whether the member is registered
276 Example: registered("mspang", "f2006") -> True
279 member = ldap_connection.member_lookup(userid)
280 return 'term' in member and term in member['term']
283 def member_terms(userid):
285 Retrieves a list of terms a member is
289 userid - the member's username
291 Returns: list of term strings
293 Example: registered('ctdalek') -> 's1993'
296 member = ldap_connection.member_lookup(userid)
297 if not 'term' in member:
300 return member['term']
306 if __name__ == '__main__':
308 from csc.common.test import *
310 # t=test m=member u=updated
311 tmname = 'Test Member'
313 tmprogram = 'Metaphysics'
314 tm2name = 'Test Member 2'
315 tm2uid = 'testmember2'
316 tm2uname = 'Test Member II'
317 tm2uprogram = 'Pseudoscience'
319 tmdict = {'cn': [tmname], 'uid': [tmuid], 'program': [tmprogram] }
320 tm2dict = {'cn': [tm2name], 'uid': [tm2uid] }
321 tm2udict = {'cn': [tm2uname], 'uid': [tm2uid], 'program': [tm2uprogram] }
323 thisterm = terms.current()
324 nextterm = terms.next(thisterm)
331 assert_equal(True, connected())
335 tmid = new(tmuid, tmname, tmprogram)
336 tm2id = new(tm2uid, tm2name)
340 assert_equal(True, registered(tmid, thisterm))
341 assert_equal(True, registered(tm2id, thisterm))
342 assert_equal(False, registered(tmid, nextterm))
347 del tmp['objectClass']
349 assert_equal(tmdict, tmp)
351 del tmp['objectClass']
353 assert_equal(tm2dict, tmp)
357 assert_equal(True, tmid in list_name(tmname).keys())
358 assert_equal(True, tm2id in list_name(tm2name).keys())
362 register(tmid, nextterm)
363 assert_equal(True, registered(tmid, nextterm))
367 assert_equal([thisterm, nextterm], member_terms(tmid))
368 assert_equal([thisterm], member_terms(tm2id))
372 assert_equal(True, tmid in list_term(thisterm).keys())
373 assert_equal(True, tmid in list_term(nextterm).keys())
374 assert_equal(True, tm2id in list_term(thisterm).keys())
375 assert_equal(False, tm2id in list_term(nextterm).keys())
380 del tmp['objectClass']
382 assert_equal(tm2dict, tmp)
392 assert_equal(False, connected())