PgSQL to LDAP transition - Complete
authorMichael Spang <mspang@uwaterloo.ca>
Mon, 16 Jul 2007 11:52:21 +0000 (07:52 -0400)
committerMichael Spang <mspang@uwaterloo.ca>
Mon, 16 Jul 2007 13:01:29 +0000 (09:01 -0400)
With this commit, the PostgreSQL database is no longer
used by CEO. All term and member information are now
retrieved and saved to the LDAP directory.

bin/ceoquery
pylib/csc/adm/accounts.py
pylib/csc/adm/members.py
pylib/csc/apps/legacy/main.py

index 629bb14..454191e 100755 (executable)
@@ -15,9 +15,9 @@ for key in os.environ.keys():
 
 os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
 
-for pathent in sys.path[:]:
-    if not pathent.find('/usr') == 0:
-        sys.path.remove(pathent)
+#for pathent in sys.path[:]:
+#    if not pathent.find('/usr') == 0:
+#        sys.path.remove(pathent)
 
 euid = os.geteuid()
 egid = os.getegid()
@@ -37,7 +37,7 @@ except Exception, e:
     sys.exit(1)
 
 def usage():
-    print "Usage: ceoquery memberlist|booklist|allmembers|allusers|termusers"
+    print "Usage: ceoquery memberlist|booklist|allusers|termusers"
 
 if len(sys.argv) < 2:
     usage()
@@ -46,14 +46,16 @@ elif sys.argv[1] == 'memberlist':
     
     current_term = terms.current()
     members = members.list_term(current_term)
-    for member in members:
-        print "%(memberid)s|%(name)s|%(program)s|%(userid)s" % member
+    for member in members.values():
+        if 'program' in member:
+            program = member['program'][0]
+        else:
+            program = ''
+        print "%s|%s|%s" % (member['cn'][0], program, member['uid'][0])
 
 elif sys.argv[1] == 'allmembers':
 
-    members = members.list_all()
-    for member in members:
-        print "%(memberid)s|%(name)s|%(program)s|%(userid)s" % member
+    pass
 
 elif sys.argv[1] == 'booklist':
 
index fc9f05f..2b28750 100644 (file)
@@ -655,18 +655,14 @@ def remove_member(username, groupname):
 
 ### Account Types ###
 
-def create_member(username, password, name, memberid):
+def create_member(username, password, name):
     """
     Creates a UNIX user account with options tailored to CSC members.
 
-    Note: The 'other' section of the GECOS field is filled with the CSC
-          memberid. This section cannot be changed by the user via chfn(1).
-
     Parameters:
         username - the desired UNIX username
         password - the desired UNIX password
         name     - the member's real name
-        memberid - the CSC member id number
 
     Exceptions:
         InvalidArgument - on bad account attributes provided
@@ -692,24 +688,20 @@ def create_member(username, password, name, memberid):
     maximum_id = cfg['member_max_id']
     home = cfg['member_home'] + '/' + username
     description = cfg['member_desc']
-    gecos_field = build_gecos(name, other=memberid)
+    gecos_field = build_gecos(name)
     shell = cfg['member_shell']
     group = cfg['member_group']
 
     return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
 
 
-def create_club(username, name, memberid):
+def create_club(username, name):
     """
     Creates a UNIX user account with options tailored to CSC-hosted clubs.
     
-    Note: The 'other' section of the GECOS field is filled with the CSC
-          memberid. This section cannot be changed by the user via chfn(1).
-
     Parameters:
         username - the desired UNIX username
         name     - the club name
-        memberid - the CSC member id number
 
     Exceptions:
         InvalidArgument - on bad account attributes provided
@@ -732,7 +724,7 @@ def create_club(username, name, memberid):
     maximum_id = cfg['club_max_id']
     home = cfg['club_home'] + '/' + username
     description = cfg['club_desc']
-    gecos_field = build_gecos(name, other=memberid)
+    gecos_field = build_gecos(name)
     shell = cfg['club_shell']
     group = cfg['club_group']
 
index a9c3f62..b87f856 100644 (file)
@@ -11,7 +11,7 @@ must also be moved into this module.
 """
 import re
 from csc.adm import terms
-from csc.backends import db, ldapi
+from csc.backends import ldapi
 from csc.common import conf
 from csc.common.excep import InvalidArgument
 
@@ -42,7 +42,6 @@ def load_configuration():
 
 ### Exceptions ###
 
-DBException = db.DBException
 ConfigurationException = conf.ConfigurationException
 
 class MemberException(Exception):
@@ -87,9 +86,6 @@ class NoSuchMember(MemberException):
 
 ### Connection Management ###
 
-# global database connection
-db_connection = db.DBConnection()
-
 # global directory connection
 ldap_connection = ldapi.LDAPConnection()
 
@@ -97,27 +93,25 @@ def connect():
     """Connect to PostgreSQL."""
 
     load_configuration()
-    db_connection.connect(cfg['server'], cfg['database'])
     ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base'])
 
 
 def disconnect():
     """Disconnect from PostgreSQL."""
 
-    db_connection.disconnect()
     ldap_connection.disconnect()
 
 
 def connected():
-    """Determine whether the db_connection has been established."""
+    """Determine whether the connection has been established."""
 
-    return db_connection.connected() and ldap_connection.connected()
+    return ldap_connection.connected()
 
 
 
 ### Member Table ###
 
-def new(uid, realname, studentid=None, program=None, mtype='user'):
+def new(uid, realname, studentid=None, program=None):
     """
     Registers a new CSC member. The member is added to the members table
     and registered for the current term.
@@ -127,23 +121,21 @@ def new(uid, realname, studentid=None, program=None, mtype='user'):
         realname  - the full real name of the member
         studentid - the student id number of the member
         program   - the program of study of the member
-        mtype     - a string describing the type of member ('user', 'club')
 
-    Returns: the memberid of the new member
+    Returns: the username of the new member
 
     Exceptions:
         DuplicateStudentID - if the student id already exists in the database
         InvalidStudentID   - if the student id is malformed
         InvalidRealName    - if the real name is malformed
 
-    Example: new("Michael Spang", program="CS") -> 3349
+    Example: new("Michael Spang", program="CS") -> "mspang"
     """
 
     # blank attributes should be NULL
     if studentid == '': studentid = None
     if program == '': program = None
     if uid == '': uid = None
-    if mtype == '': mtype = None
 
     # check the student id format
     if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
@@ -154,92 +146,61 @@ def new(uid, realname, studentid=None, program=None, mtype='user'):
         raise InvalidRealName(realname)
 
     # check for duplicate student id
-    member = db_connection.select_member_by_studentid(studentid) or \
-            ldap_connection.member_search_studentid(studentid)
+    member = ldap_connection.member_search_studentid(studentid)
     if member:
         raise DuplicateStudentID(studentid)
 
     # check for duplicate userid
-    member = db_connection.select_member_by_userid(uid) or \
-            ldap_connection.user_lookup(uid)
+    member = ldap_connection.user_lookup(uid)
     if member:
         raise InvalidArgument("uid", uid, "duplicate uid")
 
-    # add the member to the database
-    memberid = db_connection.insert_member(realname, studentid, program, userid=uid)
-
     # add the member to the directory
     ldap_connection.member_add(uid, realname, studentid, program)
 
-    # register them for this term in the database
-    db_connection.insert_term(memberid, terms.current())
-
     # register them for this term in the directory
     member = ldap_connection.member_lookup(uid)
     member['term'] = [ terms.current() ]
     ldap_connection.user_modify(uid, member)
 
-    # commit the database transaction
-    db_connection.commit()
-
-    return memberid
+    return uid
 
 
-def get(memberid):
-    """
-    Look up attributes of a member by memberid.
-
-    Returns: a dictionary of attributes
-
-    Example: get(3349) -> {
-                 'memberid': 3349,
-                 'name': 'Michael Spang',
-                 'program': 'Computer Science',
-                 ...
-             }
-    """
-
-    return db_connection.select_member_by_id(memberid)
-
-
-def get_userid(userid):
+def get(userid):
     """
     Look up attributes of a member by userid.
 
-    Parameters:
-        userid - the UNIX user id
-
     Returns: a dictionary of attributes
 
     Example: get('mspang') -> {
-                 'memberid': 3349,
-                 'name': 'Michael Spang',
-                 'program': 'Computer Science',
+                 'cn': [ 'Michael Spang' ],
+                 'program': [ 'Computer Science' ],
                  ...
              }
     """
 
-    return db_connection.select_member_by_userid(userid)
+    return ldap_connection.user_lookup(userid)
 
 
 def get_studentid(studentid):
     """
-    Look up attributes of a member by studnetid.
+    Look up attributes of a member by studentid.
 
     Parameters:
         studentid - the student ID number
 
-    Returns: a dictionary of attributes
+    Returns: a dict of members
     
     Example: get(...) -> {
-                 'memberid': 3349,
-                 'name': 'Michael Spang',
-                 'program': 'Computer Science',
+                'mspang': {
+                    'name': [ 'Michael Spang' ],
+                    'program': [ 'Computer Science' ],
+                 }
                  ...
              }
     """
 
-    return db_connection.select_member_by_studentid(studentid)
+    return ldap_connection.member_search_studentid(studentid)
 
 
 def list_term(term):
@@ -249,19 +210,16 @@ def list_term(term):
     Parameters:
         term - the term to match members against
 
-    Returns: a list of member dictionaries
+    Returns: a list of members
 
-    Example: list_term('f2006'): -> [
-                 { 'memberid': 3349, ... },
-                 { 'memberid': ... }.
+    Example: list_term('f2006'): -> {
+                 'mspang': { 'cn': 'Michael Spang', ... },
+                 'ctdalek': { 'cn': 'Calum T. Dalek', ... },
                  ...
-             ]
+             }
     """
 
-    # retrieve a list of memberids in term
-    memberlist = db_connection.select_members_by_term(term)
-
-    return memberlist.values()
+    return ldap_connection.member_search_term(term)
 
 
 def list_name(name):
@@ -273,123 +231,52 @@ def list_name(name):
 
     Returns: a list of member dictionaries
 
-    Example: list_name('Spang'): -> [
-                 { 'memberid': 3349, ... },
-                 { 'memberid': ... },
+    Example: list_name('Spang'): -> {
+                 'mspang': { 'cn': 'Michael Spang', ... },
                  ...
              ]
     """
 
-    # retrieve a list of memberids matching name
-    memberlist = db_connection.select_members_by_name(name)
-
-    return memberlist.values()
+    return ldap_connection.member_search_name(name)
 
 
-def list_all():
-    """
-    Builds a list of all members.
-    
-    Returns: a list of member dictionaries
-    """
-
-    # retrieve a list of members
-    memberlist = db_connection.select_all_members()
-
-    return memberlist.values()
-
-
-def delete(memberid):
+def delete(userid):
     """
     Erase all records of a member.
 
     Note: real members are never removed from the database
 
-    Returns: attributes and terms of the member in a tuple
+    Returns: ldap entry of the member
 
     Exceptions:
-        NoSuchMember - if the member id does not exist
+        NoSuchMember - if the user id does not exist
 
-    Example: delete(0) -> ({ 'memberid': 0, name: 'Calum T. Dalek' ...}, ['s1993'])
+    Example: delete('ctdalek') -> { 'cn': [ 'Calum T. Dalek' ], 'term': ['s1993'], ... }
     """
 
     # save member data
-    member = db_connection.select_member_by_id(memberid)
+    member = ldap_connection.user_lookup(userid)
 
     # bail if not found
     if not member:
-        raise NoSuchMember(memberid)
-
-    term_list = db_connection.select_terms(memberid)
-
-    # remove data from the db
-    db_connection.delete_term_all(memberid)
-    db_connection.delete_member(memberid)
-    db_connection.commit()
+        raise NoSuchMember(userid)
 
     # remove data from the directory
-    if member and member['userid']:
-        uid = member['userid']
-        ldap_connection.user_delete(uid)
-
-    return (member, term_list)
-
-
-def update(member):
-    """
-    Update CSC member attributes.
-
-    Parameters:
-        member - a dictionary with member attributes as returned by get,
-                 possibly omitting some attributes. member['memberid']
-                 must exist and be valid. None is NULL.
-
-    Exceptions:
-        NoSuchMember       - if the member id does not exist
-        InvalidStudentID   - if the student id number is malformed
-        DuplicateStudentID - if the student id number exists 
-
-    Example: update( {'memberid': 3349, userid: 'mspang'} )
-    """
-
-    if member.has_key('studentid') and member['studentid'] is not None:
+    uid = member['uid'][0]
+    ldap_connection.user_delete(uid)
 
-        studentid = member['studentid']
-        
-        # check the student id format
-        if studentid is not None and not re.match(cfg['studentid_regex'], str(studentid)):
-            raise InvalidStudentID(studentid)
-
-        # check for duplicate student id
-        dupmember = db_connection.select_member_by_studentid(studentid)
-        if dupmember:
-            raise DuplicateStudentID(studentid)
-
-    # not specifying memberid is a bug
-    if not member.has_key('memberid'):
-        raise Exception("no member specified in call to update")
-    memberid = member['memberid']
-
-    # see if member exists
-    if not get(memberid):
-        raise NoSuchMember(memberid)
-
-    # do the update
-    db_connection.update_member(member)
-
-    # commit the transaction
-    db_connection.commit()
+    return member
 
 
 
 ### Term Table ###
 
-def register(memberid, term_list):
+def register(userid, term_list):
     """
     Registers a member for one or more terms.
 
     Parameters:
-        memberid  - the member id number
+        userid  - the member's username
         term_list - the term to register for, or a list of terms
 
     Exceptions:
@@ -403,13 +290,12 @@ def register(memberid, term_list):
     if type(term_list) in (str, unicode):
         term_list = [ term_list ]
 
-    ldap_member = None
-    db_member = get(memberid)
-    if db_member['userid']:
-        uid = db_member['userid']
-        ldap_member = ldap_connection.member_lookup(uid)
-        if ldap_member and 'term' not in ldap_member:
-            ldap_member['term'] = []
+    ldap_member = ldap_connection.member_lookup(userid)
+    if ldap_member and 'term' not in ldap_member:
+        ldap_member['term'] = []
+
+    if not ldap_member:
+        raise NoSuchMember(userid)
 
     for term in term_list:
 
@@ -417,52 +303,48 @@ def register(memberid, term_list):
         if not re.match('^[wsf][0-9]{4}$', term):
             raise InvalidTerm(term)
 
-        # add term to database
-        db_connection.insert_term(memberid, term)
-
         # add the term to the directory
-        if ldap_member:
-            ldap_member['term'].append(term)
+        ldap_member['term'].append(term)
 
-    if ldap_member:
-        ldap_connection.user_modify(uid, ldap_member)
+    ldap_connection.user_modify(userid, ldap_member)
 
-    db_connection.commit()
 
-
-def registered(memberid, term):
+def registered(userid, term):
     """
     Determines whether a member is registered
     for a term.
 
     Parameters:
-        memberid - the member id number
+        userid   - the member's username
         term     - the term to check
 
     Returns: whether the member is registered
 
-    Example: registered(3349, "f2006") -> True
+    Example: registered("mspang", "f2006") -> True
     """
 
-    return db_connection.select_term(memberid, term) is not None
+    member = ldap_connection.member_lookup(userid)
+    return 'term' in member and term in member['term']
 
 
-def member_terms(memberid):
+def member_terms(userid):
     """
     Retrieves a list of terms a member is
     registered for.
 
     Parameters:
-        memberid - the member id number
+        userid - the member's username
 
     Returns: list of term strings
 
-    Example: registered(0) -> 's1993'
+    Example: registered('ctdalek') -> 's1993'
     """
 
-    terms_list = db_connection.select_terms(memberid)
-    terms_list.sort(terms.compare)
-    return terms_list
+    member = ldap_connection.member_lookup(userid)
+    if not 'term' in member:
+        return []
+    else:
+        return member['term']
 
 
 
@@ -484,9 +366,9 @@ if __name__ == '__main__':
     tm2usid = '00000002'
     tm2uprogram = 'Pseudoscience'
 
-    tmdict = {'name': tmname, 'userid': tmuid, 'program': tmprogram, 'type': 'user', 'studentid': tmsid }
-    tm2dict = {'name': tm2name, 'userid': tm2uid, 'program': None, 'type': 'user', 'studentid': tm2sid }
-    tm2udict = {'name': tm2uname, 'userid': tm2uid, 'program': tm2uprogram, 'type': 'user', 'studentid': tm2usid }
+    tmdict = {'cn': [tmname], 'uid': [tmuid], 'program': [tmprogram], 'studentid': [tmsid] }
+    tm2dict = {'cn': [tm2name], 'uid': [tm2uid], 'studentid': [tm2sid] }
+    tm2udict = {'cn': [tm2uname], 'uid': [tm2uid], 'program': [tm2uprogram], 'studentid': [tm2usid] }
 
     thisterm = terms.current()
     nextterm = terms.next(thisterm)
@@ -500,21 +382,17 @@ if __name__ == '__main__':
     success()
 
     dmid = get_studentid(tmsid)
-    if dmid: delete(dmid['memberid'])
+    if tmuid in dmid: delete(dmid[tmuid]['uid'][0])
     dmid = get_studentid(tm2sid)
-    if dmid: delete(dmid['memberid'])
+    if tm2uid in dmid: delete(dmid[tm2uid]['uid'][0])
     dmid = get_studentid(tm2usid)
-    if dmid: delete(dmid['memberid'])
+    if tm2uid in dmid: delete(dmid[tm2uid]['uid'][0])
 
     test(new)
     tmid = new(tmuid, tmname, tmsid, tmprogram)
     tm2id = new(tm2uid, tm2name, tm2sid)
     success()
 
-    tmdict['memberid'] = tmid
-    tm2dict['memberid'] = tm2id
-    tm2udict['memberid'] = tm2id
-
     test(registered)
     assert_equal(True, registered(tmid, thisterm))
     assert_equal(True, registered(tm2id, thisterm))
@@ -522,19 +400,19 @@ if __name__ == '__main__':
     success()
 
     test(get)
-    assert_equal(tmdict, get(tmid))
-    assert_equal(tm2dict, get(tm2id))
+    tmp = get(tmid)
+    del tmp['objectClass']
+    del tmp['term']
+    assert_equal(tmdict, tmp)
+    tmp = get(tm2id)
+    del tmp['objectClass']
+    del tmp['term']
+    assert_equal(tm2dict, tmp)
     success()
 
     test(list_name)
-    assert_equal(True, tmid in [ x['memberid'] for x in list_name(tmname) ])
-    assert_equal(True, tm2id in [ x['memberid'] for x in list_name(tm2name) ])
-    success()
-
-    test(list_all)
-    allmembers = list_all()
-    assert_equal(True, tmid in [ x['memberid'] for x in allmembers ])
-    assert_equal(True, tm2id in [ x['memberid'] for x in allmembers ])
+    assert_equal(True, tmid in list_name(tmname).keys())
+    assert_equal(True, tm2id in list_name(tm2name).keys())
     success()
 
     test(register)
@@ -548,24 +426,28 @@ if __name__ == '__main__':
     success()
 
     test(list_term)
-    assert_equal(True, tmid in [ x['memberid'] for x in list_term(thisterm) ])
-    assert_equal(True, tmid in [ x['memberid'] for x in list_term(nextterm) ])
-    assert_equal(True, tm2id in [ x['memberid'] for x in list_term(thisterm) ])
-    assert_equal(False, tm2id in [ x['memberid'] for x in list_term(nextterm) ])
+    assert_equal(True, tmid in list_term(thisterm).keys())
+    assert_equal(True, tmid in list_term(nextterm).keys())
+    assert_equal(True, tm2id in list_term(thisterm).keys())
+    assert_equal(False, tm2id in list_term(nextterm).keys())
     success()
 
-    test(update)
-    update(tm2udict)
-    assert_equal(tm2udict, get(tm2id))
-    success()
-
-    test(get_userid)
-    assert_equal(tm2udict, get_userid(tm2uid))
+    test(get)
+    tmp = get(tm2id)
+    del tmp['objectClass']
+    del tmp['term']
+    assert_equal(tm2dict, tmp)
     success()
 
     test(get_studentid)
-    assert_equal(tm2udict, get_studentid(tm2usid))
-    assert_equal(tmdict, get_studentid(tmsid))
+    tmp = get_studentid(tm2sid)[tm2uid]
+    del tmp['objectClass']
+    del tmp['term']
+    assert_equal(tm2dict, tmp)
+    tmp = get_studentid(tmsid)[tmuid]
+    del tmp['objectClass']
+    del tmp['term']
+    assert_equal(tmdict, tmp)
     success()
 
     test(delete)
index 64b2d50..8ac4b24 100644 (file)
@@ -18,21 +18,56 @@ from csc.common.excep import InvalidArgument
 BORDER_COLOR = curses.COLOR_RED
 
 
+def read_uid(wnd):
+    """Read a username."""
+
+    prompt = 'Username:'
+    return inputbox(wnd, prompt, 36)
+
+def read_member(wnd):
+    """Looks up a member."""
+
+    # connect the members module to its backend if necessary
+    if not members.connected(): members.connect()
+
+    uid = read_uid(wnd)
+    if not uid or uid.lower() == 'exit':
+        return
+
+    member = members.get(uid)
+    if not member:
+        msgbox(wnd, "Invalid username: %s" % uid)
+        return
+
+    # display user
+    display_member_details(wnd, member)
+
+    return member
+
+
+def action_library(wnd):
+    """Display a link to the library."""
+    msgbox(wnd, "Please visit library.csclub.uwaterloo.ca")
+
 def action_new_member(wnd):
     """Interactively add a new member."""
 
     userid, studentid, program = '', None, ''
 
+    msgbox(wnd, "Membership is $2.00 CDN. Please ensure\n"
+                "the money is desposited in the safe\n"
+                "before continuing.")
+
     # read the name
-    prompt = "      Name: "
+    prompt = "New member's full name: "
     realname = inputbox(wnd, prompt, 18)
 
-    # abort if no username is entered
+    # abort if no name is entered
     if not realname or realname.lower() == 'exit':
         return False
 
     # read the student id
-    prompt = "Student id:"
+    prompt = "New member's student ID:"
     while studentid is None or (re.search("[^0-9]", studentid) and not studentid.lower() == 'exit'):
         studentid = inputbox(wnd, prompt, 18)
 
@@ -44,7 +79,7 @@ def action_new_member(wnd):
         studentid = None
 
     # read the program of study
-    prompt = "   Program:"
+    prompt = "New member's program of study:"
     program = inputbox(wnd, prompt, 18)
 
     # abort if exit is entered
@@ -52,7 +87,7 @@ def action_new_member(wnd):
         return False
 
     # read user id
-    prompt = "Userid:"
+    prompt = "New member's UWdir username:"
     while userid == '':
         userid = inputbox(wnd, prompt, 18)
 
@@ -65,10 +100,10 @@ def action_new_member(wnd):
 
     # attempt to create the member
     try:
-        memberid = members.new(userid, realname, studentid, program)
+        members.new(userid, realname, studentid, program)
 
-        msgbox(wnd, "Success! Your memberid is %s.  You are now registered\n"
-                    % memberid + "for the " + terms.current() + " term.")
+        msgbox(wnd, "Success! Your username is %s.  You are now registered\n"
+                    % userid + "for the " + terms.current() + " term.")
 
     except members.InvalidStudentID:
         msgbox(wnd, "Invalid student ID: %s" % studentid)
@@ -90,26 +125,25 @@ def action_new_member(wnd):
 def action_term_register(wnd):
     """Interactively register a member for a term."""
 
-    memberid, term = '', ''
-
-    # read the member id
-    prompt = 'Enter memberid ("exit" to cancel):'
-    memberuserid = inputbox(wnd, prompt, 36)
+    term = ''
 
-    if not memberuserid or memberuserid.lower() == 'exit':
+    member = read_member(wnd)
+    if not member:
         return False
+    uid = member['uid'][0]
 
-    member = get_member_memberid_userid(wnd, memberuserid)
-    if not member: return False
+    # verify member
+    prompt = "Is this the correct member?"
+    answer = None
+    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
+        answer = inputbox(wnd, prompt, 28) 
 
-    memberid = member['memberid']
-    term_list = members.member_terms(memberid)
-    
-    # display user
-    display_member_details(wnd, member, term_list)
+    # user abort
+    if answer == "exit":
+        return False
 
     # read the term
-    prompt = "Which term to register for ([fws]20nn):"
+    prompt = "Which term to register for ([wsf]20nn):"
     while not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit':
         term = inputbox(wnd, prompt, 41) 
 
@@ -118,17 +152,17 @@ def action_term_register(wnd):
         return False
 
     # already registered?
-    if members.registered(memberid, term):
+    if members.registered(uid, term):
         msgbox(wnd, "You are already registered for term " + term)
         return False
 
     try:
 
         # attempt to register
-        members.register(memberid, term)
+        members.register(uid, term)
         
-        # display success message [sic]
-        msgbox(wnd, "Your are now registered for term " + term)
+        # display success message
+        msgbox(wnd, "You are now registered for term " + term)
 
     except members.InvalidTerm:
         msgbox(wnd, "Term is not valid: %s" % term)
@@ -139,23 +173,22 @@ def action_term_register(wnd):
 def action_term_register_multiple(wnd):
     """Interactively register a member for multiple terms."""
 
-    memberid, base, num = '', '', None
-
-    # read the member id
-    prompt = 'Enter memberid ("exit" to cancel):'
-    memberuserid = inputbox(wnd, prompt, 36)
+    base, num = '', None
 
-    if not memberuserid or memberuserid.lower() == 'exit':
+    member = read_member(wnd)
+    if not member:
         return False
+    uid = member['uid'][0]
 
-    member = get_member_memberid_userid(wnd, memberuserid)
-    if not member: return False
+    # verify member
+    prompt = "Is this the correct member?"
+    answer = None
+    while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
+        answer = inputbox(wnd, prompt, 28) 
 
-    memberid = member['memberid']
-    term_list = members.member_terms(memberid)
-    
-    # display user
-    display_member_details(wnd, member, term_list)
+    # user abort
+    if answer == "exit":
+        return False
 
     # read the base
     prompt = "Which term to start registering ([fws]20nn):"
@@ -182,14 +215,14 @@ def action_term_register_multiple(wnd):
 
     # already registered?
     for term in term_list:
-        if members.registered(memberid, term):
+        if members.registered(uid, term):
             msgbox(wnd, "You are already registered for term " + term)
             return False
 
     try:
 
         # attempt to register all terms
-        members.register(memberid, term_list)
+        members.register(uid, term_list)
         
         # display success message [sic]
         msgbox(wnd, "Your are now registered for terms: " + ", ".join(term_list))
@@ -200,30 +233,6 @@ def action_term_register_multiple(wnd):
     return False
 
 
-def repair_account(wnd, memberid, userid):
-    """Attemps to repair an account."""
-
-    if not accounts.connected(): accounts.connect()
-
-    member = members.get(memberid)
-    exists, haspw = accounts.status(userid)
-
-    if not exists:
-        password = input_password(wnd)
-        accounts.create_member(userid, password, member['name'], memberid)
-        msgbox(wnd, "Account created (where the hell did it go, anyway?)\n"
-                "If your homedir still exists, it will not be inaccessible to you,\n"
-                "please contact systems-committee@csclub.uwaterloo.ca to get this resolved.\n")
-
-    elif not haspw:
-        password = input_password(wnd)
-        accounts.add_password(userid, password)
-        msgbox(wnd, "Password added to account.")
-
-    else:
-        msgbox(wnd, "No problems to repair.")
-
-
 def input_password(wnd):
 
     # password input loop
@@ -249,24 +258,16 @@ def input_password(wnd):
 def action_create_account(wnd):
     """Interactively create an account for a member."""
     
-    memberid, userid = '', ''
-
-    # read the member id
-    prompt = 'Enter member ID (exit to cancel):'
-    memberid = inputbox(wnd, prompt, 35)
-
-    if not memberid or memberid.lower() == 'exit':
+    member = read_member(wnd)
+    if not member:
         return False
 
-    member = get_member_memberid_userid(wnd, memberid)
-    if not member: return False
+    # member already has an account?
+    if not accounts.connected(): accounts.connect()
+    if 'posixAccount' in member['objectClass']:
+        msgbox(wnd, "Account already exists.")
+        return False
 
-    memberid = member['memberid']
-    term_list = members.member_terms(memberid)
-    
-    # display the member
-    display_member_details(wnd, member, term_list)
-    
     # verify member
     prompt = "Is this the correct member?"
     answer = None
@@ -282,30 +283,9 @@ def action_create_account(wnd):
         msgbox(wnd, "I suggest searching for the member by userid or name from the main menu.")
         return False
 
-    # member already has an account?
-    if not accounts.connected(): accounts.connect()
-    if member['userid'] and accounts.status(member['userid'])[0]:
-
-        userid = member['userid']
-        msgbox(wnd, "Member " + str(memberid) + " already has an account: " + member['userid'] + "\n"
-                    "Attempting to repair it. Contact the sysadmin if there are still problems." )
-
-        repair_account(wnd, memberid, userid)
-
-        return False
-
-
-    if member['userid']:
-        userid = member['userid']
-
-    # read user id
-    prompt = "Userid:"
-    while userid == '':
-        userid = inputbox(wnd, prompt, 18) 
-
-    # user abort
-    if userid is None or userid.lower() == 'exit':
-        return False
+    msgbox(wnd, "Ensure the member has signed the machine\n"
+                "usage policy. Accounts of users who have\n"
+                "not signed will be suspended if discovered.")
 
     # read password
     password = input_password(wnd)
@@ -313,7 +293,7 @@ def action_create_account(wnd):
     # create the UNIX account
     try:
         if not accounts.connected(): accounts.connect()
-        accounts.create_member(userid, password, member['name'], memberid)
+        accounts.create_member(member['uid'][0], password, member['cn'][0])
     except accounts.NameConflict, e:
         msgbox(wnd, str(e))
         return False
@@ -329,86 +309,53 @@ def action_create_account(wnd):
     except accounts.KrbException, e:
         msgbox(wnd, "Error creating Kerberos principal - Contact the Systems Administrator: %s" % e)
         return False
-        
-    # now update the CEO database with the username
-    members.update( {'memberid': memberid, 'userid': userid} )
 
     # success
-    msgbox(wnd, "Please run 'addhomedir " + userid + "'.")
+    msgbox(wnd, "Please run 'addhomedir " + member['uid'][0] + "'.")
     msgbox(wnd, "Success! Your account has been added")
 
     return False
 
 
-def display_member_details(wnd, member, term_list):
+def display_member_details(wnd, member):
     """Display member attributes in a message box."""
 
     # clone and sort term_list
-    term_list = list(term_list)
+    if 'term' in member:
+        term_list = list(member['term'])
+    else:
+        term_list = []
     term_list.sort( terms.compare )
 
     # labels for data
     id_label, studentid_label, name_label = "ID:", "StudentID:", "Name:"
-    program_label, userid_label, terms_label = "Program:", "User ID:", "Terms:"
+    program_label, terms_label = "Program:", "Terms:"
 
-    # format it all into a massive string
-    message =  "%8s %-20s %10s %-10s (user)\n" % (name_label, member['name'], id_label, member['memberid']) + \
-               "%8s %-20s %10s %-10s\n" % (program_label, member['program'], studentid_label, member['studentid'])
+    if 'program' in member:
+        program = member['program'][0]
+    else:
+        program = None
 
-    if member['userid']:
-        message += "%8s %s\n" % (userid_label, member['userid'])
+    if 'studentid' in member:
+        studentid = member['studentid'][0]
     else:
-        message += 'No user ID.\n'
+        studentid = None
+
+    # format it all into a massive string
+    message =  "%8s %-20s %10s %-10s\n" % (name_label, member['cn'][0], id_label, member['uid'][0]) + \
+               "%8s %-20s %10s %-10s\n" % (program_label, program, studentid_label, studentid)
 
     message += "%s %s" % (terms_label, " ".join(term_list))
 
     # display the string in a message box
     msgbox(wnd, message)
-
-
-def get_member_memberid_userid(wnd, memberuserid):
-    """Retrieve member attributes by member of user id."""
-
-    # connect the members module to its backends
-    if not members.connected(): members.connect()
-
-    # retrieve member data
-
-    if re.match('^[0-9]*$', memberuserid):
-
-        # numeric memberid, look it up
-        memberid = int(memberuserid)
-        member = members.get( memberid )
-        if not member:
-            msgbox(wnd, '%s is an invalid memberid' % memberuserid)
-
-    else:
-
-        # non-numeric memberid: try userids
-        member = members.get_userid( memberuserid )
-        if not member:
-            msgbox(wnd, "%s is an invalid account userid" % memberuserid)
-            
-    return member
     
 
 def action_display_member(wnd):
     """Interactively display a member."""
     
-    # read the member id
-    prompt = 'Memberid: '
-    memberid = inputbox(wnd, prompt, 36)
-
-    if not memberid or memberid.lower() == 'exit':
-        return False
-
-    member = get_member_memberid_userid(wnd, memberid)
-    if not member: return False
-    term_list = members.member_terms( member['memberid'] )
-
-    # display the details in a window
-    display_member_details(wnd, member, term_list)
-
+    if not members.connected(): members.connect()
+    member = read_member(wnd)
     return False
 
 
@@ -430,14 +377,26 @@ def format_members(member_list):
 
     # clone and sort member_list
     member_list = list(member_list)
-    member_list.sort( lambda x, y: x['memberid']-y['memberid'] )
+    member_list.sort( lambda x, y: cmp(x['uid'], y['uid']) )
 
     buf = ''
     
     for member in member_list:
-        attrs = ( member['memberid'], member['name'], member['studentid'],
-                member['type'], member['program'], member['userid'] )
-        buf += "%4d %50s %10s %10s \n%55s %10s\n\n" % attrs
+        if 'uid' in member:
+            uid = member['uid'][0]
+        else:
+            uid = None
+        if 'program' in member:
+            program = member['program'][0]
+        else:
+            program = None
+        if 'studentid' in member:
+            studentid = member['studentid'][0]
+        else:
+            studentid = None
+        attrs = ( uid, member['cn'][0],
+                studentid, program )
+        buf += "%10s %30s %10s\n%41s\n\n" % attrs
 
     return buf
 
@@ -463,7 +422,7 @@ def action_list_term(wnd):
     member_list = members.list_term(term)
 
     # format the data into a mess of text
-    buf = format_members(member_list)
+    buf = format_members(member_list.values())
 
     # display the mass of text with a pager
     page( buf )
@@ -491,7 +450,7 @@ def action_list_name(wnd):
     member_list = members.list_name(name)
 
     # format the data into a mess of text
-    buf = format_members(member_list)
+    buf = format_members(member_list.values())
 
     # display the mass of text with a pager
     page( buf )
@@ -516,14 +475,10 @@ def action_list_studentid(wnd):
     if not members.connected(): members.connect()
     
     # retrieve a list of members for term
-    member = members.get_studentid(studentid)
-    if member != None:
-        member_list = [ members.get_studentid(studentid) ]
-    else:
-        member_list = []
+    member_list = members.get_studentid(studentid)
 
     # format the data into a mess of text
-    buf = format_members(member_list)
+    buf = format_members(member_list.values())
 
     # display the mass of text with a pager
     page( buf )
@@ -551,8 +506,7 @@ top_menu = [
     ( "Search for a member by name", action_list_name ),
     ( "Search for a member by student id", action_list_studentid ),
     ( "Create an account", action_create_account ),
-    ( "Re Create an account", action_create_account ),
-    ( "Library functions", null_callback ),
+    ( "Library functions", action_library ),
     ( "Exit", exit_callback ),
 ]