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 NoAvailableIDs - when the ID range is exhausted
489 GroupException - when not connected
490 LDAPException - on LDAP failure
492 Returns: the gid number of the new group
494 Example: create_group('ninjas', 10000, 14999)
499 raise AccountException("Not connected to LDAP and Kerberos")
501 # check groupname format
502 if not groupname or not re.match(cfg['groupname_regex'], groupname):
503 raise InvalidArgument("groupname", groupname, "expected format %s" % repr(cfg['groupname_regex']))
505 # load defaults for unspecified parameters
506 if not minimum_id and maximum_id:
507 minimum_id = cfg['group_min_id']
508 maximum_id = cfg['group_max_id']
509 if description == '':
510 description = cfg['group_desc']
512 check_name_usage(groupname)
514 # determine the first available groupid
515 groupid = first_available_id(cfg['group_min_id'], cfg['group_max_id'])
517 raise NoAvailableIDs(minimum_id, maximum_id)
519 ### Group creation ###
521 # create the LDAP entry
522 ldap_connection.group_add(groupname, groupid, description)
527 def delete_group(groupname):
531 Returns: the deleted LDAP information
536 raise AccountException("Not connected to LDAP")
539 ldap_state = ldap_connection.group_lookup(groupname)
541 # fail if no data is found in either LDAP or Kerberos
543 raise NoSuchGroup(groupname, "LDAP")
545 ### Group deletion ###
547 # delete the LDAP entry
549 ldap_connection.group_delete(groupname)
554 def check_membership(username, groupname):
556 Determines whether an account is a member of a group
557 by checking the group's member list and the user's
560 Returns: True if username is a member of groupname
563 check_account_status(username)
564 check_group_status(groupname)
566 group_data = ldap_connection.group_lookup(groupname)
567 user_data = ldap_connection.user_lookup(username)
569 group_members = get_members(groupname, group_data)
570 group_id = int(group_data['gidNumber'][0])
571 user_group = int(user_data['gidNumber'][0])
573 return username in group_members or group_id == user_group
576 def get_members(groupname, group_data=None):
578 Retrieve a list of members of a group. This list
579 will not include accounts that are members because
580 their gidNumber attribute matches the group's.
583 group_data - result of a previous LDAP lookup on groupname (internal)
585 Returns: a list of usernames
588 check_group_status(groupname)
591 group_data = ldap_connection.group_lookup(groupname)
593 if 'memberUid' in group_data:
594 group_members = group_data['memberUid']
601 def add_member(username, groupname):
603 Add an account to the list of group members.
605 Returns: False if the user was already a member, else True
608 check_account_status(username)
609 check_group_status(groupname)
611 group_data = ldap_connection.group_lookup(groupname)
612 group_members = get_members(groupname, group_data)
614 if groupname in group_members:
617 group_members.append(username)
618 group_data['memberUid'] = group_members
619 ldap_connection.group_modify(groupname, group_data)
624 def remove_member(username, groupname):
626 Removes an account from the list of group members.
628 Returns: True if the user was a member, else False
631 check_account_status(username)
632 check_group_status(groupname)
634 group_data = ldap_connection.group_lookup(groupname)
635 group_members = get_members(groupname, group_data)
637 if username not in group_members:
640 while username in group_members:
641 group_members.remove(username)
643 group_data['memberUid'] = group_members
644 ldap_connection.group_modify(groupname, group_data)
649 ### Account Types ###
651 def create_member(username, password, name, memberid):
653 Creates a UNIX user account with options tailored to CSC members.
655 Note: The 'other' section of the GECOS field is filled with the CSC
656 memberid. This section cannot be changed by the user via chfn(1).
659 username - the desired UNIX username
660 password - the desired UNIX password
661 name - the member's real name
662 memberid - the CSC member id number
665 InvalidArgument - on bad account attributes provided
667 Returns: the uid number of the new account
674 raise AccountException("not connected to LDAP and Kerberos")
676 # check username format
677 if not username or not re.match(cfg['username_regex'], username):
678 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
680 # check password length
681 if not password or len(password) < cfg['min_password_length']:
682 raise InvalidArgument("password", "<hidden>", "too short (minimum %d characters)" % cfg['min_password_length'])
684 minimum_id = cfg['member_min_id']
685 maximum_id = cfg['member_max_id']
686 home = cfg['member_home'] + '/' + username
687 description = cfg['member_desc']
688 gecos_field = build_gecos(name, other=memberid)
689 shell = cfg['member_shell']
690 group = cfg['member_group']
692 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
695 def create_club(username, name, memberid):
697 Creates a UNIX user account with options tailored to CSC-hosted clubs.
699 Note: The 'other' section of the GECOS field is filled with the CSC
700 memberid. This section cannot be changed by the user via chfn(1).
703 username - the desired UNIX username
705 memberid - the CSC member id number
708 InvalidArgument - on bad account attributes provided
710 Returns: the uid number of the new account
717 raise AccountException("not connected to LDAP and Kerberos")
719 # check username format
720 if not username or not re.match(cfg['username_regex'], username):
721 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
724 minimum_id = cfg['club_min_id']
725 maximum_id = cfg['club_max_id']
726 home = cfg['club_home'] + '/' + username
727 description = cfg['club_desc']
728 gecos_field = build_gecos(name, other=memberid)
729 shell = cfg['club_shell']
730 group = cfg['club_group']
732 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
735 def create_adm(username, name):
737 Creates a UNIX user account with options tailored to long-lived
738 administrative accounts (e.g. vp, www, sysadmin, etc).
741 username - the desired UNIX username
742 name - a descriptive name or purpose
745 InvalidArgument - on bad account attributes provided
747 Returns: the uid number of the new account
754 raise AccountException("not connected to LDAP and Kerberos")
756 # check username format
757 if not username or not re.match(cfg['username_regex'], username):
758 raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex']))
761 minimum_id = cfg['admin_min_id']
762 maximum_id = cfg['admin_max_id']
763 home = cfg['admin_home'] + '/' + username
764 description = cfg['admin_desc']
765 gecos_field = build_gecos(name)
766 shell = cfg['admin_shell']
767 group = cfg['admin_group']
769 return create(username, name, minimum_id, maximum_id, home, password, description, gecos_field, shell, group)
773 ### Miscellaneous Helpers ###
775 def check_name_usage(name):
777 Helper function: Ensures a user or group name does not exist in either
778 Kerberos, LDAP, or through calls to libc and NSS. This is used prior to
779 creating an accout or group to determine if the name is free.
782 name - the user or group name to check for
785 NameConflict - if the name was found anywhere
788 # see if user exists in LDAP
789 if ldap_connection.user_lookup(name):
790 raise NameConflict(name, "account", "LDAP")
792 # see if group exists in LDAP
793 if ldap_connection.group_lookup(name):
794 raise NameConflict(name, "group", "LDAP")
796 # see if user exists in Kerberos
797 principal = name + '@' + cfg['realm']
798 if krb_connection.get_principal(principal):
799 raise NameConflict(name, "account", "KRB")
801 # see if user exists by getpwnam(3)
804 raise NameConflict(name, "account", "NSS")
808 # see if group exists by getgrnam(3)
811 raise NameConflict(name, "group", "NSS")
816 def check_account_status(username, require_ldap=True, require_krb=False):
817 """Helper function to verify that an account exists."""
820 raise AccountException("Not connected to LDAP and Kerberos")
821 if require_ldap and not ldap_connection.user_lookup(username):
822 raise NoSuchAccount(username, "LDAP")
823 if require_krb and not krb_connection.get_principal(username):
824 raise NoSuchAccount(username, "KRB")
827 def check_group_status(groupname):
828 """Helper function to verify that a group exists."""
831 raise AccountException("Not connected to LDAP and Kerberos")
832 if not ldap_connection.group_lookup(groupname):
833 raise NoSuchGroup(groupname, "LDAP")
836 def parse_gecos(gecos_data):
838 Build a dictionary out of a chfn(1) style GECOS string.
841 gecos_data - a gecos string formatted by chfn(1)
843 Returns: a dictinoary of components
845 Example: parse_gecos('Michael Spang,,,') -> {
846 'fullname': 'Michael Spang',
854 # silently remove erroneous colons
855 while ':' in gecos_data:
856 index = gecos_data.find(':')
857 gecos_data = gecos_data[:index] + gecos_data[index+1:]
859 gecos_vals = gecos_data.split(',', 4)
860 gecos_vals.extend([ None ] * (5-len(gecos_vals)))
861 gecos_keys = ['fullname', 'roomnumber', 'workphone',
862 'homephone', 'other' ]
863 return dict((gecos_keys[i], gecos_vals[i]) for i in xrange(5))
866 def build_gecos(fullname=None, roomnumber=None, workphone=None, homephone=None, other=None):
868 Build a chfn(1)-style GECOS field from its components.
873 fullname - GECOS full name
874 roomnumber - GECOS room number
875 workphone - GECOS work phone
876 homephone - GECOS home phone
879 Returns: string appropriate for a GECOS field value
882 # check first four params for illegal chars
883 args = (fullname, roomnumber, workphone, homephone)
884 names = ('fullname', 'roomnumber', 'workphone', 'homephone')
885 for index in xrange(4):
886 for badchar in (',', ':', '='):
887 if args[index] and badchar in str(args[index]):
888 raise InvalidArgument(names[index], args[index], "invalid characters")
890 # check other for illegal chars
891 if other and ':' in str(other):
892 raise InvalidArgument('other', other, "invalid characters")
895 if fullname is not None:
896 gecos_data = str(fullname)
897 fields = [ fullname, roomnumber, workphone, homephone, other ]
898 for idx in xrange(len(fields), 0, -1):
899 if not fields[idx-1]:
903 while None in fields:
904 fields[fields.index(None)] = ''
905 return ','.join(map(str, fields))
908 def check_id_nss(ugid):
909 """Helper to ensure there is no account or group with an ID."""
926 def first_available_id(minimum, maximum):
928 Determines the first available id within a range.
930 To be "available", there must be neither a user
931 with the id nor a group with the id.
934 minimum - smallest id that may be returned
935 maximum - largest id that may be returned
937 Returns: the id, or None if there are none available
939 Example: first_available_id(20000, 40000) -> 20018
942 # get lists of used uids and gids in LDAP
943 uids = ldap_connection.used_uids(minimum, maximum)
944 gids = ldap_connection.used_gids(minimum, maximum)
946 # iterate through the lists and return the first available
947 for ugid in xrange(minimum, maximum+1):
948 if ugid not in uids and ugid not in gids and check_id_nss(ugid):
951 # no id found within the range
958 if __name__ == '__main__':
961 from csc.common.test import *
963 def test_exists(name):
964 return ldap_connection.user_lookup(name) is not None, \
965 ldap_connection.group_lookup(name) is not None, \
966 krb_connection.get_principal(name) is not None
968 # t=test u=user m=member a=adminv c=club
969 # g=group r=real e=expected n=new
971 turname = 'Test User'
972 tunrname = 'User Test'
973 tudesc = 'May be deleted'
974 tuhome = '/home/testuser'
975 tunhome = '/users/testuser'
976 tushell = '/bin/false'
977 tunshell = '/bin/true'
978 tugecos = 'Test User,,,'
979 tungecos = 'User Test,,,'
980 tmname = 'testmember'
981 tmrname = 'Test Member'
984 tcrname = 'Test Club'
989 tgdesc = 'Test Group'
992 tpw = str(random.randint(10**30, 10**31-1))
994 tgecos_args = 'a','b','c','d','e'
1001 delete(tuname); delete(tmname)
1002 delete(tcname); delete(taname)
1003 delete_group(tgname)
1004 except (NoSuchAccount, NoSuchGroup):
1008 create(tuname, turname, minid, maxid, tuhome, tpw, tudesc, tugecos, tushell)
1009 exists = test_exists(tuname)
1010 expected = (True, True, True)
1011 assert_equal(expected, exists)
1015 create_member(tmname, tpw, tmrname, tmmid)
1016 exists = test_exists(tmname)
1017 expected = (True, False, True)
1018 assert_equal(expected, exists)
1022 create_club(tcname, tmrname, tmmid)
1023 exists = test_exists(tcname)
1024 expected = (True, False, False)
1025 assert_equal(expected, exists)
1029 create_adm(taname, tarname)
1030 exists = test_exists(taname)
1031 expected = (True, False, False)
1032 assert_equal(expected, exists)
1036 create_group(tgname, minid, maxid, tgdesc)
1037 exists = test_exists(tgname)
1038 expected = (False, True, False)
1039 assert_equal(expected, exists)
1043 assert_equal((True, True), status(tmname))
1044 assert_equal((True, False), status(tcname))
1047 test(reset_password)
1048 reset_password(tuname, str(int(tpw)/2))
1049 reset_password(tmname, str(int(tpw)/3))
1050 negative(reset_password, (tcname,str(int(tpw)/4)), NoSuchAccount, "club should not have password")
1051 negative(reset_password, (taname,str(int(tpw)/5)), NoSuchAccount, "club should not have password")
1055 tuuid = get_uid(tuname)
1056 assert_equal(True, int(tuuid) >= 0)
1060 tugid = get_gid(tuname)
1061 assert_equal(True, int(tugid) >= 0)
1065 ugecos = get_gecos(tuname)
1066 assert_equal(tugecos, ugecos)
1070 update_gecos(tuname, tungecos)
1071 ugecos = get_gecos(tuname)
1072 assert_equal(tungecos, ugecos)
1076 ushell = get_shell(tuname)
1077 assert_equal(tushell, ushell)
1081 update_shell(tuname, tunshell, False)
1082 ushell = get_shell(tuname)
1083 assert_equal(ushell, tunshell)
1087 urname = get_name(tuname)
1088 assert_equal(turname, urname)
1092 update_name(tuname, tunrname)
1093 urname = get_name(tuname)
1094 assert_equal(urname, tunrname)
1098 uhome = get_home(tuname)
1099 assert_equal(tuhome, uhome)
1103 update_home(tuname, tunhome)
1104 urhome = get_home(tuname)
1105 assert_equal(urhome, tunhome)
1109 members = get_members(tgname)
1111 assert_equal(expected, members)
1114 test(check_membership)
1115 member = check_membership(tuname, tgname)
1116 assert_equal(False, member)
1117 member = check_membership(tuname, tuname)
1118 assert_equal(True, member)
1122 add_member(tuname, tgname)
1123 assert_equal(True, check_membership(tuname, tgname))
1124 assert_equal([tuname], get_members(tgname))
1128 assert_equal(True, remove_member(tuname, tgname))
1129 assert_equal(False, check_membership(tuname, tgname))
1130 assert_equal(False, remove_member(tuname, tgname))
1134 assert_equal(tgecos, build_gecos(*tgecos_args))
1138 gecos_dict = parse_gecos(tgecos)
1139 assert_equal(tgecos, build_gecos(**gecos_dict))
1144 exists = test_exists(tuname)
1145 expected = (False, False, False)
1146 assert_equal(expected, exists)
1148 exists = test_exists(tmname)
1149 assert_equal(expected, exists)
1151 exists = test_exists(tcname)
1152 assert_equal(expected, exists)
1154 exists = test_exists(taname)
1155 assert_equal(expected, exists)
1159 delete_group(tgname)
1160 exists = test_exists(tgname)
1161 expected = (False, False, False)
1162 assert_equal(expected, exists)