2 UNIX Accounts Administration
4 This module contains functions for creating, deleting, and manipulating
5 UNIX user accounts and account groups in the CSC LDAP directory.
7 import re, pwd, grp, os
8 from csc.common import conf
9 from csc.common.excep import InvalidArgument
10 from csc.backends import ldapi, krb
15 CONFIG_FILE = '/etc/csc/accounts.cf'
20 """Helper to load the accounts configuration. You need not call this."""
22 string_fields = [ 'member_shell', 'member_home', 'member_desc',
23 'member_group', 'club_shell', 'club_home', 'club_desc',
24 'club_group', 'admin_shell', 'admin_home', 'admin_desc',
25 'admin_group', 'group_desc', 'username_regex', 'groupname_regex',
26 'shells_file', 'server_url', 'users_base', 'groups_base',
27 'admin_bind_dn', 'admin_bind_pw', 'realm', 'admin_principal',
29 numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id',
30 'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id',
31 'group_max_id', 'min_password_length' ]
33 # read configuration file
34 cfg_tmp = conf.read(CONFIG_FILE)
36 # verify configuration (not necessary, but prints a useful error)
37 conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
38 conf.check_integer_fields(CONFIG_FILE, numeric_fields, cfg_tmp)
40 # update the current configuration with the loaded values
47 KrbException = krb.KrbException
48 LDAPException = ldapi.LDAPException
49 ConfigurationException = conf.ConfigurationException
51 class AccountException(Exception):
52 """Base exception class for account-related errors."""
54 class NoAvailableIDs(AccountException):
55 """Exception class for exhausted userid ranges."""
56 def __init__(self, minid, maxid):
57 self.minid, self.maxid = minid, maxid
59 return "No free ID pairs found in range [%d, %d]" % (self.minid, self.maxid)
61 class NameConflict(AccountException):
62 """Exception class for name conflicts with existing accounts/groups."""
63 def __init__(self, name, nametype, source):
64 self.name, self.nametype, self.source = name, nametype, source
66 return 'Name Conflict: %s "%s" already exists in %s' % (self.nametype, self.name, self.source)
68 class NoSuchAccount(AccountException):
69 """Exception class for missing LDAP entries for accounts."""
70 def __init__(self, account, source):
71 self.account, self.source = account, source
73 return 'Account "%s" not found in %s' % (self.account, self.source)
75 class NoSuchGroup(AccountException):
76 """Exception class for missing LDAP entries for groups."""
77 def __init__(self, account, source):
78 self.account, self.source = account, source
80 return 'Account "%s" not found in %s' % (self.account, self.source)
84 ### Connection Management ###
86 ldap_connection = ldapi.LDAPConnection()
87 krb_connection = krb.KrbConnection()
90 """Connect to LDAP and Kerberos and load configuration. You must call before anything else."""
94 # connect to the LDAP server
95 ldap_connection.connect(cfg['server_url'], cfg['admin_bind_dn'], cfg['admin_bind_pw'], cfg['users_base'], cfg['groups_base'])
97 # connect to the Kerberos master server
98 krb_connection.connect(cfg['admin_principal'], cfg['admin_keytab'])
102 """Disconnect from LDAP and Kerberos. Call this before quitting."""
104 ldap_connection.disconnect()
105 krb_connection.disconnect()
109 """Determine whether a connection has been established."""
111 return ldap_connection.connected() and krb_connection.connected()
115 ### General Account Management ###
117 def create(username, name, minimum_id, maximum_id, home, password=None, description='', gecos='', shell=None, group=None):
119 Creates a UNIX user account. This involves first creating an LDAP
120 directory entry, then creating a Kerberos principal.
122 The UID/GID namespace may be divided into ranges according to account type
123 or purpose. This function requires such a range to allocate ids from.
125 If no password is specified or password is None, no Kerberos principal
126 will be created and the account will not be capable of direct login.
127 This is desirable for administrative and club accounts.
129 If no group is specified, a new group will be created with the same name
130 as the user. The uid of the created user and gid of the created group
131 will be numerically equal. There is generally no reason to specify a
132 group. Furthermore, only groups present in the directory are allowed.
134 If an account is relevant to only one system and will not own files on
135 NFS, please use adduser(8) on the relevant system instead.
137 Generally do not directly use this function. The create_member(),
138 create_club(), and create_adm() functions will fill in most of
139 the details for you and may do additional checks.
142 username - UNIX username for the account
143 name - common name LDAP attribute
144 minimum_id - the smallest UID/GID to assign
145 maximum_id - the largest UID/GID to assign
146 home - home directory LDAP attribute
147 password - password for the account
148 description - description LDAP attribute
149 gecos - gecos LDAP attribute
150 shell - user shell LDAP attribute
151 group - primary group for account
154 NameConflict - when the name conflicts with an existing account
155 NoSuchGroup - when the group parameter corresponds to no group
156 NoAvailableIDs - when the ID range is exhausted
157 AccountException - when not connected
159 Returns: the uid number of the new account
161 Example: create('mspang', 'Michael Spang', 20000, 39999,
162 '/users/mspang', 'secret', 'CSC Member Account',
163 build_gecos('Michael Spang', other='3349'),
164 '/bin/bash', 'users')
169 raise AccountException("Not connected to LDAP and Kerberos")
171 # check for path characters in username (. and /)
172 if re.search('[\\./]', username):
173 raise InvalidArgument("username", username, "invalid characters")
175 check_name_usage(username)
177 # determine the first available userid
178 userid = first_available_id(minimum_id, maximum_id)
180 raise NoAvailableIDs(minimum_id, maximum_id)
182 # determine the account's default group
184 group_data = ldap_connection.group_lookup(group)
186 raise NoSuchGroup(group, "LDAP")
187 gid = int(group_data['gidNumber'][0])
191 ### User creation ###
193 if not ldap_connection.user_lookup(username):
195 # create the LDAP entry
196 ldap_connection.account_add(username, name, userid, gid, home, shell, gecos, description)
200 # add the required attribute to the LDAP entry
201 ldap_connection.member_add_account(username, userid, gid, home, shell, gecos)
203 # create a user group if no other group was specified
205 ldap_connection.group_add(username, gid)
207 # create the Kerberos principal
209 principal = username + '@' + cfg['realm']
210 krb_connection.add_principal(principal, password)
215 def delete(username):
217 Deletes a UNIX account. Both LDAP entries and Kerberos principals that
218 match username are deleted. A group with the same name is deleted too,
219 if it exists and has the same id as the account.
221 Returns: tuple with deleted LDAP and Kerberos information
222 note: the Kerberos keys are not recoverable
227 raise AccountException("Not connected to LDAP and Kerberos")
229 # build principal name from username
230 principal = username + '@' + cfg['realm']
233 ldap_state = ldap_connection.user_lookup(username)
234 krb_state = krb_connection.get_principal(principal)
235 group_state = ldap_connection.group_lookup(username)
237 # don't delete group unless the gid matches the account's uid
238 if not ldap_state or group_state and ldap_state['uidNumber'][0] != group_state['gidNumber'][0]:
241 # fail if no data is found in either LDAP or Kerberos
242 if not ldap_state and not krb_state:
243 raise NoSuchAccount(username, "LDAP/Kerberos")
245 ### User deletion ###
247 # delete the LDAP entries
249 ldap_connection.user_delete(username)
251 ldap_connection.group_delete(username)
253 # delete the Kerberos principal
255 krb_connection.delete_principal(principal)
257 return ldap_state, group_state, krb_state
260 def status(username):
262 Checks if an account exists.
264 Returns: a boolean 2-tuple (exists, has_password)
267 ldap_state = ldap_connection.account_lookup(username)
268 krb_state = krb_connection.get_principal(username)
269 return (ldap_state is not None, krb_state is not None)
272 def add_password(username, password):
274 Creates a principal for an existing, passwordless account.
277 username - a UNIX account username
278 password - a password for the acccount
280 check_account_status(username)
281 ldap_state = ldap_connection.user_lookup(username)
282 if int(ldap_state['uidNumber'][0]) < 1000:
283 raise AccountException("Attempted to add password to a system account")
284 krb_connection.add_principal(username, password)
287 def reset_password(username, newpassword):
289 Changes a user's password.
292 username - a UNIX account username
293 newpassword - a new password for the account
295 check_account_status(username, require_krb=True)
296 krb_connection.change_password(username, newpassword)
299 def get_uid(username):
301 Determine the numeric uid of an account.
303 Returns: a uid as an int
305 check_account_status(username)
306 account_data = ldap_connection.user_lookup(username)
307 return int(account_data['uidNumber'][0])
310 def get_gid(username):
312 Determine the numeric gid of an account (default group).
314 Returns: a gid as an int
316 check_account_status(username)
317 account_data = ldap_connection.user_lookup(username)
318 return int(account_data['gidNumber'][0])
321 def get_gecos(username, account_data=None):
323 Retrieve GECOS information of a user.
325 Returns: raw gecos data as a string, or None
327 check_account_status(username)
329 account_data = ldap_connection.user_lookup(username)
330 if 'gecos' in account_data:
331 return account_data['gecos'][0]
336 def update_gecos(username, gecos_data):
338 Set GECOS information for a user. The LDAP 'cn' attribute
339 is also updated with the user's full name.
341 See build_gecos() and parse_gecos() for help dealing with
342 the chfn(1) GECOS format.
344 Use update_name() to update the name porition, as it will update
345 the LDAP 'cn' atribute as well.
348 username - a UNIX account username
349 gecos_data - a raw gecos string
351 Example: update_gecos('mspang', build_gecos('Mike Spang'))
353 check_account_status(username)
354 entry = ldap_connection.user_lookup(username)
355 entry['gecos'] = [ gecos_data ]
356 ldap_connection.user_modify(username, entry)
359 def get_name(username):
361 Get the real name of a user. Note that this name is usually stored
362 in both the 'cn' attribute and the 'gecos' attribute, and they
363 may differ. This function will always return the first in the'cn'
364 version. If there are multiple, the first in the list is returned.
366 Returns: the common name associated with the account
368 check_account_status(username)
369 account_data = ldap_connection.user_lookup(username)
370 return account_data['cn'][0]
373 def update_name(username, name, update_gecos=True):
375 Set the real name of a user. This name will be updated in both
376 the GECOS field and the common name field. If there are multiple
377 common names, they will *all* be overwritten with the provided name.
380 username - the UNIX account usernmae
381 nane - new real name for the account
382 update_gecos - whether to update gecos field
384 check_account_status(username)
385 account_data = ldap_connection.user_lookup(username)
386 account_data['cn'] = [ name ]
388 gecos_dict = parse_gecos(get_gecos(username, account_data))
389 gecos_dict['fullname'] = name
390 account_data['gecos'] = [ build_gecos(**gecos_dict) ]
391 ldap_connection.user_modify(username, account_data)
394 def get_shell(username):
396 Retrieve a user's shell.
398 Returns: the path to the shell, or None
400 check_account_status(username)
401 account_data = ldap_connection.user_lookup(username)
402 if 'loginShell' not in account_data or len(account_data['loginShell']) < 1:
404 return account_data['loginShell'][0]
407 def update_shell(username, shell, check=True):
412 username - the UNIX account username
413 shell - the new shell for the user
414 check - whether to check if the shell is in the shells file
417 InvalidArgument - on nonexistent shell
420 # reject nonexistent or nonexecutable shells
421 if not os.access(shell, os.X_OK) or not os.path.isfile(shell):
422 raise InvalidArgument("shell", shell, "not an executable file")
427 shells = open(cfg['shells_file']).read().split("\n")
428 shells = [ x for x in shells if x and x[0] == '/' and '#' not in x ]
430 # reject shells that aren't in the shells file (usually /etc/shells)
431 if check and shell not in shells:
432 raise InvalidArgument("shell", shell, "is not in %s" % cfg['shells_file'])
434 check_account_status(username)
435 account_data = ldap_connection.user_lookup(username)
436 account_data['loginShell'] = [ shell ]
437 ldap_connection.user_modify(username, account_data)
440 def get_home(username):
442 Get the home directory of a user.
444 Returns: path to the user's home directory
446 check_account_status(username)
447 account_data = ldap_connection.user_lookup(username)
448 return account_data['homeDirectory'][0]
451 def update_home(username, home):
453 Set the home directory of a user.
456 username - the UNIX account username
457 home - new home directory for the user
459 check_account_status(username)
460 if not home[0] == '/':
461 raise InvalidArgument('home', home, 'relative path')
462 account_data = ldap_connection.user_lookup(username)
463 account_data['homeDirectory'] = [ home ]
464 ldap_connection.user_modify(username, account_data)
468 ### General Group Management ###
470 def create_group(groupname, minimum_id=None, maximum_id=None, description=''):
472 Creates a UNIX group. This involves adding an entry to LDAP.
474 The UID/GID namespace may be divided into ranges according to group
475 type or purpose. This function accept such a range to allocate ids from.
476 If none is specified, it will use the default from the configuration file.
478 If a group needs directory accounts as members, or if the group will
479 own files on NFS, you must add it to the directory with this function.
481 If a group is relevant to only a single system and does not need any
482 directory accounts as members, create it with the addgroup(8) utility
483 for just that system instead.
485 If you do not specify description, the default will be used. If no
486 description at all is wanted, set description to None.
489 groupname - UNIX group name
490 minimum_id - the smallest GID to assign
491 maximum_id - the largest GID to assign
492 description - description LDAP attribute
495 NoAvailableIDs - when the ID range is exhausted
496 GroupException - when not connected
497 LDAPException - on LDAP failure
499 Returns: the gid number of the new group
501 Example: create_group('ninjas', 10000, 14999)
506 raise AccountException("Not connected to LDAP and Kerberos")
508 # check groupname format
509 if not groupname or not re.match(cfg['groupname_regex'], groupname):
510 raise InvalidArgument("groupname", groupname, "expected format %s" % repr(cfg['groupname_regex']))
512 # load defaults for unspecified parameters
513 if not minimum_id and maximum_id:
514 minimum_id = cfg['group_min_id']
515 maximum_id = cfg['group_max_id']
516 if description == '':
517 description = cfg['group_desc']
519 check_name_usage(groupname)
521 # determine the first available groupid
522 groupid = first_available_id(cfg['group_min_id'], cfg['group_max_id'])
524 raise NoAvailableIDs(minimum_id, maximum_id)
526 ### Group creation ###
528 # create the LDAP entry
529 ldap_connection.group_add(groupname, groupid, description)
534 def delete_group(groupname):
538 Returns: the deleted LDAP information
543 raise AccountException("Not connected to LDAP")
546 ldap_state = ldap_connection.group_lookup(groupname)
548 # fail if no data is found in either LDAP or Kerberos
550 raise NoSuchGroup(groupname, "LDAP")
552 ### Group deletion ###
554 # delete the LDAP entry
556 ldap_connection.group_delete(groupname)
561 def check_membership(username, groupname):
563 Determines whether an account is a member of a group
564 by checking the group's member list and the user's
567 Returns: True if username is a member of groupname
570 check_account_status(username)
571 check_group_status(groupname)
573 group_data = ldap_connection.group_lookup(groupname)
574 user_data = ldap_connection.user_lookup(username)
576 group_members = get_members(groupname, group_data)
577 group_id = int(group_data['gidNumber'][0])
578 user_group = int(user_data['gidNumber'][0])
580 return username in group_members or group_id == user_group
583 def get_members(groupname, group_data=None):
585 Retrieve a list of members of a group. This list
586 will not include accounts that are members because
587 their gidNumber attribute matches the group's.
590 group_data - result of a previous LDAP lookup on groupname (internal)
592 Returns: a list of usernames
595 check_group_status(groupname)
598 group_data = ldap_connection.group_lookup(groupname)
600 if 'memberUid' in group_data:
601 group_members = group_data['memberUid']
608 def add_member(username, groupname):
610 Add an account to the list of group members.
612 Returns: False if the user was already a member, else True
615 check_account_status(username)
616 check_group_status(groupname)
618 group_data = ldap_connection.group_lookup(groupname)
619 group_members = get_members(groupname, group_data)
621 if groupname in group_members:
624 group_members.append(username)
625 group_data['memberUid'] = group_members
626 ldap_connection.group_modify(groupname, group_data)
631 def remove_member(username, groupname):
633 Removes an account from the list of group members.
635 Returns: True if the user was a member, else False
638 check_account_status(username)
639 check_group_status(groupname)
641 group_data = ldap_connection.group_lookup(groupname)
642 group_members = get_members(groupname, group_data)
644 if username not in group_members:
647 while username in group_members:
648 group_members.remove(username)
650 group_data['memberUid'] = group_members
651 ldap_connection.group_modify(groupname, group_data)
656 ### Account Types ###
658 def create_member(username, password, name):
660 Creates a UNIX user account with options tailored to CSC members.
663 username - the desired UNIX username
664 password - the desired UNIX password
665 name - the member's real name
668 InvalidArgument - on bad account attributes provided
670 Returns: the uid number of the new account
677 raise AccountException("not connected to LDAP and Kerberos")
679 # check username format
680 if not username or not re.match(cfg['username_regex'], username):
681 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
683 # check password length
684 if not password or len(password) < cfg['min_password_length']:
685 raise InvalidArgument("password", "<hidden>", "too short (minimum %d characters)" % cfg['min_password_length'])
687 minimum_id = cfg['member_min_id']
688 maximum_id = cfg['member_max_id']
689 home = cfg['member_home'] + '/' + username
690 description = cfg['member_desc']
691 gecos_field = build_gecos(name)
692 shell = cfg['member_shell']
693 group = cfg['member_group']
695 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
698 def create_club(username, name):
700 Creates a UNIX user account with options tailored to CSC-hosted clubs.
703 username - the desired UNIX username
707 InvalidArgument - on bad account attributes provided
709 Returns: the uid number of the new account
716 raise AccountException("not connected to LDAP and Kerberos")
718 # check username format
719 if not username or not re.match(cfg['username_regex'], username):
720 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
723 minimum_id = cfg['club_min_id']
724 maximum_id = cfg['club_max_id']
725 home = cfg['club_home'] + '/' + username
726 description = cfg['club_desc']
727 gecos_field = build_gecos(name)
728 shell = cfg['club_shell']
729 group = cfg['club_group']
731 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
734 def create_adm(username, name):
736 Creates a UNIX user account with options tailored to long-lived
737 administrative accounts (e.g. vp, www, sysadmin, etc).
740 username - the desired UNIX username
741 name - a descriptive name or purpose
744 InvalidArgument - on bad account attributes provided
746 Returns: the uid number of the new account
753 raise AccountException("not connected to LDAP and Kerberos")
755 # check username format
756 if not username or not re.match(cfg['username_regex'], username):
757 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
760 minimum_id = cfg['admin_min_id']
761 maximum_id = cfg['admin_max_id']
762 home = cfg['admin_home'] + '/' + username
763 description = cfg['admin_desc']
764 gecos_field = build_gecos(name)
765 shell = cfg['admin_shell']
766 group = cfg['admin_group']
768 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
772 ### Miscellaneous Helpers ###
774 def check_name_usage(name):
776 Helper function: Ensures a user or group name does not exist in either
777 Kerberos, LDAP, or through calls to libc and NSS. This is used prior to
778 creating an accout or group to determine if the name is free.
781 name - the user or group name to check for
784 NameConflict - if the name was found anywhere
787 # see if user exists in LDAP
788 if ldap_connection.account_lookup(name):
789 raise NameConflict(name, "account", "LDAP")
791 # see if group exists in LDAP
792 if ldap_connection.group_lookup(name):
793 raise NameConflict(name, "group", "LDAP")
795 # see if user exists in Kerberos
796 principal = name + '@' + cfg['realm']
797 if krb_connection.get_principal(principal):
798 raise NameConflict(name, "account", "KRB")
800 # see if user exists by getpwnam(3)
803 raise NameConflict(name, "account", "NSS")
807 # see if group exists by getgrnam(3)
810 raise NameConflict(name, "group", "NSS")
815 def check_account_status(username, require_ldap=True, require_krb=False):
816 """Helper function to verify that an account exists."""
819 raise AccountException("Not connected to LDAP and Kerberos")
820 if require_ldap and not ldap_connection.account_lookup(username):
821 raise NoSuchAccount(username, "LDAP")
822 if require_krb and not krb_connection.get_principal(username):
823 raise NoSuchAccount(username, "KRB")
826 def check_group_status(groupname):
827 """Helper function to verify that a group exists."""
830 raise AccountException("Not connected to LDAP and Kerberos")
831 if not ldap_connection.group_lookup(groupname):
832 raise NoSuchGroup(groupname, "LDAP")
835 def parse_gecos(gecos_data):
837 Build a dictionary out of a chfn(1) style GECOS string.
840 gecos_data - a gecos string formatted by chfn(1)
842 Returns: a dictinoary of components
844 Example: parse_gecos('Michael Spang,,,') -> {
845 'fullname': 'Michael Spang',
853 # silently remove erroneous colons
854 while ':' in gecos_data:
855 index = gecos_data.find(':')
856 gecos_data = gecos_data[:index] + gecos_data[index+1:]
858 gecos_vals = gecos_data.split(',', 4)
859 gecos_vals.extend([ None ] * (5-len(gecos_vals)))
860 gecos_keys = ['fullname', 'roomnumber', 'workphone',
861 'homephone', 'other' ]
862 return dict((gecos_keys[i], gecos_vals[i]) for i in xrange(5))
865 def build_gecos(fullname=None, roomnumber=None, workphone=None, homephone=None, other=None):
867 Build a chfn(1)-style GECOS field from its components.
872 fullname - GECOS full name
873 roomnumber - GECOS room number
874 workphone - GECOS work phone
875 homephone - GECOS home phone
878 Returns: string appropriate for a GECOS field value
881 # check first four params for illegal chars
882 args = (fullname, roomnumber, workphone, homephone)
883 names = ('fullname', 'roomnumber', 'workphone', 'homephone')
884 for index in xrange(4):
885 for badchar in (',', ':', '='):
886 if args[index] and badchar in str(args[index]):
887 raise InvalidArgument(names[index], args[index], "invalid characters")
889 # check other for illegal chars
890 if other and ':' in str(other):
891 raise InvalidArgument('other', other, "invalid characters")
894 if fullname is not None:
895 gecos_data = str(fullname)
896 fields = [ fullname, roomnumber, workphone, homephone, other ]
897 for idx in xrange(len(fields), 0, -1):
898 if not fields[idx-1]:
902 while None in fields:
903 fields[fields.index(None)] = ''
904 return ','.join(map(str, fields))
907 def check_id_nss(ugid):
908 """Helper to ensure there is no account or group with an ID."""
925 def first_available_id(minimum, maximum):
927 Determines the first available id within a range.
929 To be "available", there must be neither a user
930 with the id nor a group with the id.
933 minimum - smallest id that may be returned
934 maximum - largest id that may be returned
936 Returns: the id, or None if there are none available
938 Example: first_available_id(20000, 40000) -> 20018
941 # get lists of used uids and gids in LDAP
942 uids = ldap_connection.used_uids(minimum, maximum)
943 gids = ldap_connection.used_gids(minimum, maximum)
945 # iterate through the lists and return the first available
946 for ugid in xrange(minimum, maximum+1):
947 if ugid not in uids and ugid not in gids and check_id_nss(ugid):
950 # no id found within the range
957 if __name__ == '__main__':
960 from csc.common.test import *
962 def test_exists(name):
963 return ldap_connection.user_lookup(name) is not None, \
964 ldap_connection.group_lookup(name) is not None, \
965 krb_connection.get_principal(name) is not None
967 # t=test u=user m=member a=adminv c=club
968 # g=group r=real e=expected n=new
970 turname = 'Test User'
971 tunrname = 'User Test'
972 tudesc = 'May be deleted'
973 tuhome = '/home/testuser'
974 tunhome = '/users/testuser'
975 tushell = '/bin/false'
976 tunshell = '/bin/true'
977 tugecos = 'Test User,,,'
978 tungecos = 'User Test,,,'
979 tmname = 'testmember'
980 tmrname = 'Test Member'
983 tcrname = 'Test Club'
988 tgdesc = 'Test Group'
991 tpw = str(random.randint(10**30, 10**31-1))
993 tgecos_args = 'a','b','c','d','e'
1000 delete(tuname); delete(tmname)
1001 delete(tcname); delete(taname)
1002 delete_group(tgname)
1003 except (NoSuchAccount, NoSuchGroup):
1007 create(tuname, turname, minid, maxid, tuhome, tpw, tudesc, tugecos, tushell)
1008 exists = test_exists(tuname)
1009 expected = (True, True, True)
1010 assert_equal(expected, exists)
1014 create_member(tmname, tpw, tmrname, tmmid)
1015 exists = test_exists(tmname)
1016 expected = (True, False, True)
1017 assert_equal(expected, exists)
1021 create_club(tcname, tmrname, tmmid)
1022 exists = test_exists(tcname)
1023 expected = (True, False, False)
1024 assert_equal(expected, exists)
1028 create_adm(taname, tarname)
1029 exists = test_exists(taname)
1030 expected = (True, False, False)
1031 assert_equal(expected, exists)
1035 create_group(tgname, minid, maxid, tgdesc)
1036 exists = test_exists(tgname)
1037 expected = (False, True, False)
1038 assert_equal(expected, exists)
1042 assert_equal((True, True), status(tmname))
1043 assert_equal((True, False), status(tcname))
1046 test(reset_password)
1047 reset_password(tuname, str(int(tpw)/2))
1048 reset_password(tmname, str(int(tpw)/3))
1049 negative(reset_password, (tcname,str(int(tpw)/4)), NoSuchAccount, "club should not have password")
1050 negative(reset_password, (taname,str(int(tpw)/5)), NoSuchAccount, "club should not have password")
1054 tuuid = get_uid(tuname)
1055 assert_equal(True, int(tuuid) >= 0)
1059 tugid = get_gid(tuname)
1060 assert_equal(True, int(tugid) >= 0)
1064 ugecos = get_gecos(tuname)
1065 assert_equal(tugecos, ugecos)
1069 update_gecos(tuname, tungecos)
1070 ugecos = get_gecos(tuname)
1071 assert_equal(tungecos, ugecos)
1075 ushell = get_shell(tuname)
1076 assert_equal(tushell, ushell)
1080 update_shell(tuname, tunshell, False)
1081 ushell = get_shell(tuname)
1082 assert_equal(ushell, tunshell)
1086 urname = get_name(tuname)
1087 assert_equal(turname, urname)
1091 update_name(tuname, tunrname)
1092 urname = get_name(tuname)
1093 assert_equal(urname, tunrname)
1097 uhome = get_home(tuname)
1098 assert_equal(tuhome, uhome)
1102 update_home(tuname, tunhome)
1103 urhome = get_home(tuname)
1104 assert_equal(urhome, tunhome)
1108 members = get_members(tgname)
1110 assert_equal(expected, members)
1113 test(check_membership)
1114 member = check_membership(tuname, tgname)
1115 assert_equal(False, member)
1116 member = check_membership(tuname, tuname)
1117 assert_equal(True, member)
1121 add_member(tuname, tgname)
1122 assert_equal(True, check_membership(tuname, tgname))
1123 assert_equal([tuname], get_members(tgname))
1127 assert_equal(True, remove_member(tuname, tgname))
1128 assert_equal(False, check_membership(tuname, tgname))
1129 assert_equal(False, remove_member(tuname, tgname))
1133 assert_equal(tgecos, build_gecos(*tgecos_args))
1137 gecos_dict = parse_gecos(tgecos)
1138 assert_equal(tgecos, build_gecos(**gecos_dict))
1143 exists = test_exists(tuname)
1144 expected = (False, False, False)
1145 assert_equal(expected, exists)
1147 exists = test_exists(tmname)
1148 assert_equal(expected, exists)
1150 exists = test_exists(tcname)
1151 assert_equal(expected, exists)
1153 exists = test_exists(taname)
1154 assert_equal(expected, exists)
1158 delete_group(tgname)
1159 exists = test_exists(tgname)
1160 expected = (False, False, False)
1161 assert_equal(expected, exists)