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 # create the LDAP entry
194 ldap_connection.user_add(username, name, userid, gid, home, shell, gecos, description)
196 # create a user group if no other group was specified
198 ldap_connection.group_add(username, gid)
200 # create the Kerberos principal
202 principal = username + '@' + cfg['realm']
203 krb_connection.add_principal(principal, password)
208 def delete(username):
210 Deletes a UNIX account. Both LDAP entries and Kerberos principals that
211 match username are deleted. A group with the same name is deleted too,
212 if it exists and has the same id as the account.
214 Returns: tuple with deleted LDAP and Kerberos information
215 note: the Kerberos keys are not recoverable
220 raise AccountException("Not connected to LDAP and Kerberos")
222 # build principal name from username
223 principal = username + '@' + cfg['realm']
226 ldap_state = ldap_connection.user_lookup(username)
227 krb_state = krb_connection.get_principal(principal)
228 group_state = ldap_connection.group_lookup(username)
230 # don't delete group unless the gid matches the account's uid
231 if not ldap_state or group_state and ldap_state['uidNumber'][0] != group_state['gidNumber'][0]:
234 # fail if no data is found in either LDAP or Kerberos
235 if not ldap_state and not krb_state:
236 raise NoSuchAccount(username, "LDAP/Kerberos")
238 ### User deletion ###
240 # delete the LDAP entries
242 ldap_connection.user_delete(username)
244 ldap_connection.group_delete(username)
246 # delete the Kerberos principal
248 krb_connection.delete_principal(principal)
250 return ldap_state, group_state, krb_state
253 def status(username):
255 Checks if an account exists.
257 Returns: a boolean 2-tuple (exists, has_password)
260 ldap_state = ldap_connection.user_lookup(username)
261 krb_state = krb_connection.get_principal(username)
262 return (ldap_state is not None, krb_state is not None)
265 def add_password(username, password):
267 Creates a principal for an existing, passwordless account.
270 username - a UNIX account username
271 password - a password for the acccount
273 check_account_status(username)
274 ldap_state = ldap_connection.user_lookup(username)
275 if int(ldap_state['uidNumber'][0]) < 1000:
276 raise AccountException("Attempted to add password to a system account")
277 krb_connection.add_principal(username, password)
280 def reset_password(username, newpassword):
282 Changes a user's password.
285 username - a UNIX account username
286 newpassword - a new password for the account
288 check_account_status(username, require_krb=True)
289 krb_connection.change_password(username, newpassword)
292 def get_uid(username):
294 Determine the numeric uid of an account.
296 Returns: a uid as an int
298 check_account_status(username)
299 account_data = ldap_connection.user_lookup(username)
300 return int(account_data['uidNumber'][0])
303 def get_gid(username):
305 Determine the numeric gid of an account (default group).
307 Returns: a gid as an int
309 check_account_status(username)
310 account_data = ldap_connection.user_lookup(username)
311 return int(account_data['gidNumber'][0])
314 def get_gecos(username, account_data=None):
316 Retrieve GECOS information of a user.
318 Returns: raw gecos data as a string, or None
320 check_account_status(username)
322 account_data = ldap_connection.user_lookup(username)
323 if 'gecos' in account_data:
324 return account_data['gecos'][0]
329 def update_gecos(username, gecos_data):
331 Set GECOS information for a user. The LDAP 'cn' attribute
332 is also updated with the user's full name.
334 See build_gecos() and parse_gecos() for help dealing with
335 the chfn(1) GEOCS format.
337 Use update_name() to update the name porition, as it will update
338 the LDAP 'cn' atribute as well.
341 username - a UNIX account username
342 gecos_data - a raw gecos string
344 Example: update_gecos('mspang', build_gecos('Mike Spang'))
346 check_account_status(username)
347 entry = ldap_connection.user_lookup(username)
348 entry['gecos'] = [ gecos_data ]
349 ldap_connection.user_modify(username, entry)
352 def get_name(username):
354 Get the real name of a user. Note that this name is usually stored
355 in both the 'cn' attribute and the 'gecos' attribute, and they
356 may differ. This function will always return the first in the'cn'
357 version. If there are multiple, the first in the list is returned.
359 Returns: the common name associated with the account
361 check_account_status(username)
362 account_data = ldap_connection.user_lookup(username)
363 return account_data['cn'][0]
366 def update_name(username, name, update_gecos=True):
368 Set the real name of a user. This name will be updated in both
369 the GECOS field and the common name field. If there are multiple
370 common names, they will *all* be overwritten with the provided name.
373 username - the UNIX account usernmae
374 nane - new real name for the account
375 update_gecos - whether to update gecos field
377 check_account_status(username)
378 account_data = ldap_connection.user_lookup(username)
379 account_data['cn'] = [ name ]
381 gecos_dict = parse_gecos(get_gecos(username, account_data))
382 gecos_dict['fullname'] = name
383 account_data['gecos'] = [ build_gecos(**gecos_dict) ]
384 ldap_connection.user_modify(username, account_data)
387 def get_shell(username):
389 Retrieve a user's shell.
391 Returns: the path to the shell, or None
393 check_account_status(username)
394 account_data = ldap_connection.user_lookup(username)
395 if 'loginShell' not in account_data or len(account_data['loginShell']) < 1:
397 return account_data['loginShell'][0]
400 def update_shell(username, shell, check=True):
405 username - the UNIX account username
406 shell - the new shell for the user
407 check - whether to check if the shell is in the shells file
410 InvalidArgument - on nonexistent shell
413 # reject nonexistent or nonexecutable shells
414 if not os.access(shell, os.X_OK) or not os.path.isfile(shell):
415 raise InvalidArgument("shell", shell, "is not a regular executable file")
420 shells = open(cfg['shells_file']).read().split("\n")
421 shells = [ x for x in shells if x and x[0] == '/' and '#' not in x ]
423 # reject shells that aren't in the shells file (usually /etc/shells)
424 if check and shell not in shells:
425 raise InvalidArgument("shell", shell, "is not in %s" % cfg['shells_file'])
427 check_account_status(username)
428 account_data = ldap_connection.user_lookup(username)
429 account_data['loginShell'] = [ shell ]
430 ldap_connection.user_modify(username, account_data)
433 def get_home(username):
435 Get the home directory of a user.
437 Returns: path to the user's home directory
439 check_account_status(username)
440 account_data = ldap_connection.user_lookup(username)
441 return account_data['homeDirectory'][0]
444 def update_home(username, home):
446 Set the home directory of a user.
449 username - the UNIX account username
450 home - new home directory for the user
452 check_account_status(username)
453 if not home[0] == '/':
454 raise InvalidArgument('home', home, 'relative path')
455 account_data = ldap_connection.user_lookup(username)
456 account_data['homeDirectory'] = [ home ]
457 ldap_connection.user_modify(username, account_data)
461 ### General Group Management ###
463 def create_group(groupname, minimum_id=None, maximum_id=None, description=''):
465 Creates a UNIX group. This involves adding an entry to LDAP.
467 The UID/GID namespace may be divided into ranges according to group
468 type or purpose. This function accept such a range to allocate ids from.
469 If none is specified, it will use the default from the configuration file.
471 If a group needs directory accounts as members, or if the group will
472 own files on NFS, you must add it to the directory with this function.
474 If a group is relevant to only a single system and does not need any
475 directory accounts as members, create it with the addgroup(8) utility
476 for just that system instead.
478 If you do not specify description, the default will be used. If no
479 description at all is wanted, set description to None.
482 groupname - UNIX group name
483 minimum_id - the smallest GID to assign
484 maximum_id - the largest GID to assign
485 description - description LDAP attribute
488 GroupExists - when the group name conflicts with an existing group
489 NoAvailableIDs - when the ID range is exhausted
490 GroupException - when not connected
491 LDAPException - on LDAP failure
493 Returns: the gid number of the new group
495 Example: create_group('ninjas', 10000, 14999)
500 raise AccountException("Not connected to LDAP and Kerberos")
502 # check groupname format
503 if not groupname or not re.match(cfg['groupname_regex'], groupname):
504 raise InvalidArgument("groupname", groupname, "expected format %s" % repr(cfg['groupname_regex']))
506 # load defaults for unspecified parameters
507 if not minimum_id and maximum_id:
508 minimum_id = cfg['group_min_id']
509 maximum_id = cfg['group_max_id']
510 if description == '':
511 description = cfg['group_desc']
513 check_name_usage(groupname)
515 # determine the first available groupid
516 groupid = first_available_id(cfg['group_min_id'], cfg['group_max_id'])
518 raise NoAvailableIDs(minimum_id, maximum_id)
520 ### Group creation ###
522 # create the LDAP entry
523 ldap_connection.group_add(groupname, groupid, description)
528 def delete_group(groupname):
532 Returns: the deleted LDAP information
537 raise AccountException("Not connected to LDAP")
540 ldap_state = ldap_connection.group_lookup(groupname)
542 # fail if no data is found in either LDAP or Kerberos
544 raise NoSuchGroup(groupname, "LDAP")
546 ### Group deletion ###
548 # delete the LDAP entry
550 ldap_connection.group_delete(groupname)
555 def check_membership(username, groupname):
557 Determines whether an account is a member of a group
558 by checking the group's member list and the user's
561 Returns: True if username is a member of groupname
564 check_account_status(username)
565 check_group_status(groupname)
567 group_data = ldap_connection.group_lookup(groupname)
568 user_data = ldap_connection.user_lookup(username)
570 group_members = get_members(groupname, group_data)
571 group_id = int(group_data['gidNumber'][0])
572 user_group = int(user_data['gidNumber'][0])
574 return username in group_members or group_id == user_group
577 def get_members(groupname, group_data=None):
579 Retrieve a list of members of a group. This list
580 will not include accounts that are members because
581 their gidNumber attribute matches the group's.
584 group_data - result of a previous LDAP lookup on groupname (internal)
586 Returns: a list of usernames
589 check_group_status(groupname)
592 group_data = ldap_connection.group_lookup(groupname)
594 if 'memberUid' in group_data:
595 group_members = group_data['memberUid']
602 def add_member(username, groupname):
604 Add an account to the list of group members.
606 Returns: False if the user was already a member, else True
609 check_account_status(username)
610 check_group_status(groupname)
612 group_data = ldap_connection.group_lookup(groupname)
613 group_members = get_members(groupname, group_data)
615 if groupname in group_members:
618 group_members.append(username)
619 group_data['memberUid'] = group_members
620 ldap_connection.group_modify(groupname, group_data)
625 def remove_member(username, groupname):
627 Removes an account from the list of group members.
629 Returns: True if the user was a member, else False
632 check_account_status(username)
633 check_group_status(groupname)
635 group_data = ldap_connection.group_lookup(groupname)
636 group_members = get_members(groupname, group_data)
638 if username not in group_members:
641 while username in group_members:
642 group_members.remove(username)
644 group_data['memberUid'] = group_members
645 ldap_connection.group_modify(groupname, group_data)
650 ### Account Types ###
652 def create_member(username, password, name, memberid):
654 Creates a UNIX user account with options tailored to CSC members.
656 Note: The 'other' section of the GECOS field is filled with the CSC
657 memberid. This section cannot be changed by the user via chfn(1).
660 username - the desired UNIX username
661 password - the desired UNIX password
662 name - the member's real name
663 memberid - the CSC member id number
666 InvalidArgument - on bad account attributes provided
668 Returns: the uid number of the new account
675 raise AccountException("not connected to LDAP and Kerberos")
677 # check username format
678 if not username or not re.match(cfg['username_regex'], username):
679 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
681 # check password length
682 if not password or len(password) < cfg['min_password_length']:
683 raise InvalidArgument("password", "<hidden>", "too short (minimum %d characters)" % cfg['min_password_length'])
685 minimum_id = cfg['member_min_id']
686 maximum_id = cfg['member_max_id']
687 home = cfg['member_home'] + '/' + username
688 description = cfg['member_desc']
689 gecos_field = build_gecos(name, other=memberid)
690 shell = cfg['member_shell']
691 group = cfg['member_group']
693 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
696 def create_club(username, name, memberid):
698 Creates a UNIX user account with options tailored to CSC-hosted clubs.
700 Note: The 'other' section of the GECOS field is filled with the CSC
701 memberid. This section cannot be changed by the user via chfn(1).
704 username - the desired UNIX username
706 memberid - the CSC member id number
709 InvalidArgument - on bad account attributes provided
711 Returns: the uid number of the new account
718 raise AccountException("not connected to LDAP and Kerberos")
720 # check username format
721 if not username or not re.match(cfg['username_regex'], username):
722 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
725 minimum_id = cfg['club_min_id']
726 maximum_id = cfg['club_max_id']
727 home = cfg['club_home'] + '/' + username
728 description = cfg['club_desc']
729 gecos_field = build_gecos(name, other=memberid)
730 shell = cfg['club_shell']
731 group = cfg['club_group']
733 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
736 def create_adm(username, name):
738 Creates a UNIX user account with options tailored to long-lived
739 administrative accounts (e.g. vp, www, sysadmin, etc).
742 username - the desired UNIX username
743 name - a descriptive name or purpose
746 InvalidArgument - on bad account attributes provided
748 Returns: the uid number of the new account
755 raise AccountException("not connected to LDAP and Kerberos")
757 # check username format
758 if not username or not re.match(cfg['username_regex'], username):
759 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
762 minimum_id = cfg['admin_min_id']
763 maximum_id = cfg['admin_max_id']
764 home = cfg['admin_home'] + '/' + username
765 description = cfg['admin_desc']
766 gecos_field = build_gecos(name)
767 shell = cfg['admin_shell']
768 group = cfg['admin_group']
770 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
774 ### Miscellaneous Helpers ###
776 def check_name_usage(name):
778 Helper function: Ensures a user or group name does not exist in either
779 Kerberos, LDAP, or through calls to libc and NSS. This is used prior to
780 creating an accout or group to determine if the name is free.
783 name - the user or group name to check for
786 NameConflict - if the name was found anywhere
789 # see if user exists in LDAP
790 if ldap_connection.user_lookup(name):
791 raise NameConflict(name, "account", "LDAP")
793 # see if group exists in LDAP
794 if ldap_connection.group_lookup(name):
795 raise NameConflict(name, "group", "LDAP")
797 # see if user exists in Kerberos
798 principal = name + '@' + cfg['realm']
799 if krb_connection.get_principal(principal):
800 raise NameConflict(name, "account", "KRB")
802 # see if user exists by getpwnam(3)
805 raise NameConflict(name, "account", "NSS")
809 # see if group exists by getgrnam(3)
812 raise NameConflict(name, "group", "NSS")
817 def check_account_status(username, require_ldap=True, require_krb=False):
818 """Helper function to verify that an account exists."""
821 raise AccountException("Not connected to LDAP and Kerberos")
822 if require_ldap and not ldap_connection.user_lookup(username):
823 raise NoSuchAccount(username, "LDAP")
824 if require_krb and not krb_connection.get_principal(username):
825 raise NoSuchAccount(username, "KRB")
828 def check_group_status(groupname):
829 """Helper function to verify that a group exists."""
832 raise AccountException("Not connected to LDAP and Kerberos")
833 if not ldap_connection.group_lookup(groupname):
834 raise NoSuchGroup(groupname, "LDAP")
837 def parse_gecos(gecos_data):
839 Build a dictionary out of a chfn(1) style GECOS string.
842 gecos_data - a gecos string formatted by chfn(1)
844 Returns: a dictinoary of components
846 Example: parse_gecos('Michael Spang,,,') -> {
847 'fullname': 'Michael Spang',
855 # silently remove erroneous colons
856 while ':' in gecos_data:
857 index = gecos_data.find(':')
858 gecos_data = gecos_data[:index] + gecos_data[index+1:]
860 gecos_vals = gecos_data.split(',', 4)
861 gecos_vals.extend([ None ] * (5-len(gecos_vals)))
862 gecos_keys = ['fullname', 'roomnumber', 'workphone',
863 'homephone', 'other' ]
864 return dict((gecos_keys[i], gecos_vals[i]) for i in xrange(5))
867 def build_gecos(fullname=None, roomnumber=None, workphone=None, homephone=None, other=None):
869 Build a chfn(1)-style GECOS field from its components.
874 fullname - GECOS full name
875 roomnumber - GECOS room number
876 workphone - GECOS work phone
877 homephone - GECOS home phone
880 Returns: string appropriate for a GECOS field value
883 # check first four params for illegal chars
884 args = (fullname, roomnumber, workphone, homephone)
885 names = ('fullname', 'roomnumber', 'workphone', 'homephone')
886 for index in xrange(4):
887 for badchar in (',', ':', '='):
888 if args[index] and badchar in str(args[index]):
889 raise InvalidArgument(names[index], args[index], "invalid characters")
891 # check other for illegal chars
892 if other and ':' in str(other):
893 raise InvalidArgument('other', other, "invalid characters")
896 if fullname is not None:
897 gecos_data = str(fullname)
898 for field in (roomnumber, workphone, homephone, other):
899 if field is not None:
900 gecos_data += ',' + str(field)
905 def check_id_nss(ugid):
906 """Helper to ensure there is no account or group with an ID."""
923 def first_available_id(minimum, maximum):
925 Determines the first available id within a range.
927 To be "available", there must be neither a user
928 with the id nor a group with the id.
931 minimum - smallest id that may be returned
932 maximum - largest id that may be returned
934 Returns: the id, or None if there are none available
936 Example: first_available_id(20000, 40000) -> 20018
939 # get lists of used uids and gids in LDAP
940 uids = ldap_connection.used_uids(minimum, maximum)
941 gids = ldap_connection.used_gids(minimum, maximum)
943 # iterate through the lists and return the first available
944 for ugid in xrange(minimum, maximum+1):
945 if ugid not in uids and ugid not in gids and check_id_nss(ugid):
948 # no id found within the range
955 if __name__ == '__main__':
958 from csc.common.test import *
960 def test_exists(name):
961 return ldap_connection.user_lookup(name) is not None, \
962 ldap_connection.group_lookup(name) is not None, \
963 krb_connection.get_principal(name) is not None
965 # t=test u=user m=member a=adminv c=club
966 # g=group r=real e=expected n=new
968 turname = 'Test User'
969 tunrname = 'User Test'
970 tudesc = 'May be deleted'
971 tuhome = '/home/testuser'
972 tunhome = '/users/testuser'
973 tushell = '/bin/false'
974 tunshell = '/bin/true'
975 tugecos = 'Test User,,,'
976 tungecos = 'User Test,,,'
977 tmname = 'testmember'
978 tmrname = 'Test Member'
981 tcrname = 'Test Club'
986 tgdesc = 'Test Group'
989 tpw = str(random.randint(10**30, 10**31-1))
991 tgecos_args = 'a','b','c','d','e'
998 delete(tuname); delete(tmname)
999 delete(tcname); delete(taname)
1000 delete_group(tgname)
1001 except (NoSuchAccount, NoSuchGroup):
1005 create(tuname, turname, minid, maxid, tuhome, tpw, tudesc, tugecos, tushell)
1006 exists = test_exists(tuname)
1007 expected = (True, True, True)
1008 assert_equal(expected, exists)
1012 create_member(tmname, tpw, tmrname, tmmid)
1013 exists = test_exists(tmname)
1014 expected = (True, False, True)
1015 assert_equal(expected, exists)
1019 create_club(tcname, tmrname, tmmid)
1020 exists = test_exists(tcname)
1021 expected = (True, False, False)
1022 assert_equal(expected, exists)
1026 create_adm(taname, tarname)
1027 exists = test_exists(taname)
1028 expected = (True, False, False)
1029 assert_equal(expected, exists)
1033 create_group(tgname, minid, maxid, tgdesc)
1034 exists = test_exists(tgname)
1035 expected = (False, True, False)
1036 assert_equal(expected, exists)
1040 assert_equal((True, True), status(tmname))
1041 assert_equal((True, False), status(tcname))
1044 test(reset_password)
1045 reset_password(tuname, str(int(tpw)/2))
1046 reset_password(tmname, str(int(tpw)/3))
1047 negative(reset_password, (tcname,str(int(tpw)/4)), NoSuchAccount, "club should not have password")
1048 negative(reset_password, (taname,str(int(tpw)/5)), NoSuchAccount, "club should not have password")
1052 tuuid = get_uid(tuname)
1053 assert_equal(True, int(tuuid) >= 0)
1057 tugid = get_gid(tuname)
1058 assert_equal(True, int(tugid) >= 0)
1062 ugecos = get_gecos(tuname)
1063 assert_equal(tugecos, ugecos)
1067 update_gecos(tuname, tungecos)
1068 ugecos = get_gecos(tuname)
1069 assert_equal(tungecos, ugecos)
1073 ushell = get_shell(tuname)
1074 assert_equal(tushell, ushell)
1078 update_shell(tuname, tunshell, False)
1079 ushell = get_shell(tuname)
1080 assert_equal(ushell, tunshell)
1084 urname = get_name(tuname)
1085 assert_equal(turname, urname)
1089 update_name(tuname, tunrname)
1090 urname = get_name(tuname)
1091 assert_equal(urname, tunrname)
1095 uhome = get_home(tuname)
1096 assert_equal(tuhome, uhome)
1100 update_home(tuname, tunhome)
1101 urhome = get_home(tuname)
1102 assert_equal(urhome, tunhome)
1106 members = get_members(tgname)
1108 assert_equal(expected, members)
1111 test(check_membership)
1112 member = check_membership(tuname, tgname)
1113 assert_equal(False, member)
1114 member = check_membership(tuname, tuname)
1115 assert_equal(True, member)
1119 add_member(tuname, tgname)
1120 assert_equal(True, check_membership(tuname, tgname))
1121 assert_equal([tuname], get_members(tgname))
1125 assert_equal(True, remove_member(tuname, tgname))
1126 assert_equal(False, check_membership(tuname, tgname))
1127 assert_equal(False, remove_member(tuname, tgname))
1131 assert_equal(tgecos, build_gecos(*tgecos_args))
1135 gecos_dict = parse_gecos(tgecos)
1136 assert_equal(tgecos, build_gecos(**gecos_dict))
1141 exists = test_exists(tuname)
1142 expected = (False, False, False)
1143 assert_equal(expected, exists)
1145 exists = test_exists(tmname)
1146 assert_equal(expected, exists)
1148 exists = test_exists(tcname)
1149 assert_equal(expected, exists)
1151 exists = test_exists(taname)
1152 assert_equal(expected, exists)
1156 delete_group(tgname)
1157 exists = test_exists(tgname)
1158 expected = (False, False, False)
1159 assert_equal(expected, exists)