diff --git a/etc/members.cf b/etc/members.cf deleted file mode 100644 index 59ac4a6..0000000 --- a/etc/members.cf +++ /dev/null @@ -1,8 +0,0 @@ -# /etc/csc/members.cf: CSC Members Configuration - -include /etc/csc/ldap.cf - -### Validation Tuning ### - -studentid_regex = "^[0-9]{8}$" -realname_regex = "^[^,:=]*$" diff --git a/pylib/csc/adm/__init__.py b/pylib/csc/adm/__init__.py index 99f59e0..2c37b1b 100644 --- a/pylib/csc/adm/__init__.py +++ b/pylib/csc/adm/__init__.py @@ -4,6 +4,5 @@ CSC Administrative Modules This module provides member and account management modules. members - member registration management functions - accounts - account administration functions terms - helper routines for manipulating terms """ diff --git a/pylib/csc/adm/accounts.py b/pylib/csc/adm/accounts.py deleted file mode 100644 index 4792931..0000000 --- a/pylib/csc/adm/accounts.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -UNIX Accounts Administration - -This module contains functions for creating, deleting, and manipulating -UNIX user accounts and account groups in the CSC LDAP directory. -""" -import re, pwd, grp, os, subprocess -from csc.common import conf -from csc.common.excep import InvalidArgument -from csc.backends import ldapi - - -### Configuration ### - -CONFIG_FILE = '/etc/csc/accounts.cf' - -cfg = {} - -def configure(): - """Helper to load the accounts configuration. You need not call this.""" - - string_fields = [ 'member_shell', 'member_home', 'member_desc', - 'member_group', 'club_shell', 'club_home', 'club_desc', - 'club_group', 'admin_shell', 'admin_home', 'admin_desc', - 'admin_group', 'group_desc', 'username_regex', 'groupname_regex', - 'shells_file', 'server_url', 'users_base', 'groups_base', - 'sasl_mech', 'sasl_realm', 'admin_bind_keytab', - 'admin_bind_userid', 'realm', 'admin_principal', 'admin_keytab' ] - numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id', - 'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id', - 'group_max_id', 'min_password_length' ] - - # read configuration file - cfg_tmp = conf.read(CONFIG_FILE) - - # verify configuration (not necessary, but prints a useful error) - conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp) - conf.check_integer_fields(CONFIG_FILE, numeric_fields, cfg_tmp) - - # update the current configuration with the loaded values - cfg.update(cfg_tmp) - - - -### Exceptions ### - -LDAPException = ldapi.LDAPException -ConfigurationException = conf.ConfigurationException - -class AccountException(Exception): - """Base exception class for account-related errors.""" - -class ChildFailed(AccountException): - def __init__(self, program, status, output): - self.program, self.status, self.output = program, status, output - def __str__(self): - msg = '%s failed with status %d' % (self.program, self.status) - if self.output: - msg += ': %s' % self.output - return msg - - -### Connection Management ### - -ldap_connection = ldapi.LDAPConnection() - -def connect(): - """Connect to LDAP and Kerberos and load configuration. You must call before anything else.""" - - configure() - - # connect to the LDAP server - ldap_connection.connect_sasl(cfg['server_url'], cfg['sasl_mech'], - cfg['sasl_realm'], cfg['admin_bind_userid'], - ('keytab', cfg['admin_bind_keytab']), cfg['users_base'], - cfg['groups_base']) - - -def disconnect(): - """Disconnect from LDAP and Kerberos. Call this before quitting.""" - - ldap_connection.disconnect() - - -### Account Types ### - -def create_member(username, password, name, program): - """ - Creates a UNIX user account with options tailored to CSC members. - - Parameters: - username - the desired UNIX username - password - the desired UNIX password - name - the member's real name - program - the member's program of study - - Exceptions: - InvalidArgument - on bad account attributes provided - - Returns: the uid number of the new account - - See: create() - """ - - # check connection - if not connected(): - raise AccountException("not connected to LDAP and Kerberos") - - # check username format - if not username or not re.match(cfg['username_regex'], username): - raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex'])) - - # check password length - if not password or len(password) < cfg['min_password_length']: - raise InvalidArgument("password", "", "too short (minimum %d characters)" % cfg['min_password_length']) - - args = [ "/usr/bin/addmember", "--stdin", username, name, program ] - addmember = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = addmember.communicate(password) - status = addmember.wait() - - if status: - raise ChildFailed("addmember", status, out+err) - - -def create_club(username, name): - """ - Creates a UNIX user account with options tailored to CSC-hosted clubs. - - Parameters: - username - the desired UNIX username - name - the club name - - Exceptions: - InvalidArgument - on bad account attributes provided - - Returns: the uid number of the new account - - See: create() - """ - - # check username format - if not username or not re.match(cfg['username_regex'], username): - raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex'])) - - args = [ "/usr/bin/addclub", username, name ] - addclub = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - out, err = addclub.communicate() - status = addclub.wait() - - if status: - raise ChildFailed("addclub", status, out+err) diff --git a/pylib/csc/adm/members.py b/pylib/csc/adm/members.py index 27ff083..bf6dbf7 100644 --- a/pylib/csc/adm/members.py +++ b/pylib/csc/adm/members.py @@ -9,31 +9,38 @@ Transactions are used in each method that modifies the database. Future changes to the members database that need to be atomic must also be moved into this module. """ -import re, ldap -from csc.adm import terms -from csc.backends import ldapi +import re, subprocess, ldap from csc.common import conf from csc.common.excep import InvalidArgument +from csc.backends import ldapi ### Configuration ### -CONFIG_FILE = '/etc/csc/members.cf' +CONFIG_FILE = '/etc/csc/accounts.cf' cfg = {} -def load_configuration(): +def configure(): """Load Members Configuration""" - string_fields = [ 'realname_regex', 'server_url', 'users_base', - 'groups_base', 'sasl_mech', 'sasl_realm', 'admin_bind_keytab', - 'admin_bind_userid' ] + string_fields = [ 'member_shell', 'member_home', 'member_desc', + 'member_group', 'club_shell', 'club_home', 'club_desc', + 'club_group', 'admin_shell', 'admin_home', 'admin_desc', + 'admin_group', 'group_desc', 'username_regex', 'groupname_regex', + 'shells_file', 'server_url', 'users_base', 'groups_base', + 'sasl_mech', 'sasl_realm', 'admin_bind_keytab', + 'admin_bind_userid', 'realm', 'admin_principal', 'admin_keytab' ] + numeric_fields = [ 'member_min_id', 'member_max_id', 'club_min_id', + 'club_max_id', 'admin_min_id', 'admin_max_id', 'group_min_id', + 'group_max_id', 'min_password_length' ] # read configuration file cfg_tmp = conf.read(CONFIG_FILE) # verify configuration conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp) + conf.check_integer_fields(CONFIG_FILE, numeric_fields, cfg_tmp) # update the current configuration with the loaded values cfg.update(cfg_tmp) @@ -43,6 +50,7 @@ def load_configuration(): ### Exceptions ### ConfigurationException = conf.ConfigurationException +LDAPException = ldapi.LDAPException class MemberException(Exception): """Base exception class for member-related errors.""" @@ -61,6 +69,14 @@ class NoSuchMember(MemberException): def __str__(self): return "Member not found: %d" % self.memberid +class ChildFailed(MemberException): + def __init__(self, program, status, output): + self.program, self.status, self.output = program, status, output + def __str__(self): + msg = '%s failed with status %d' % (self.program, self.status) + if self.output: + msg += ': %s' % self.output + return msg ### Connection Management ### @@ -71,7 +87,7 @@ ldap_connection = ldapi.LDAPConnection() def connect(): """Connect to LDAP.""" - load_configuration() + configure() ldap_connection.connect_sasl(cfg['server_url'], cfg['sasl_mech'], cfg['sasl_realm'], cfg['admin_bind_userid'], ('keytab', cfg['admin_bind_keytab']), cfg['users_base'], @@ -92,6 +108,45 @@ def connected(): ### Members ### +def create_member(username, password, name, program): + """ + Creates a UNIX user account with options tailored to CSC members. + + Parameters: + username - the desired UNIX username + password - the desired UNIX password + name - the member's real name + program - the member's program of study + + Exceptions: + InvalidArgument - on bad account attributes provided + + Returns: the uid number of the new account + + See: create() + """ + + # check connection + if not connected(): + raise MemberException("not connected to LDAP and Kerberos") + + # check username format + if not username or not re.match(cfg['username_regex'], username): + raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex'])) + + # check password length + if not password or len(password) < cfg['min_password_length']: + raise InvalidArgument("password", "", "too short (minimum %d characters)" % cfg['min_password_length']) + + args = [ "/usr/bin/addmember", "--stdin", username, name, program ] + addmember = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = addmember.communicate(password) + status = addmember.wait() + + if status: + raise ChildFailed("addmember", status, out+err) + + def get(userid): """ Look up attributes of a member by userid. @@ -256,6 +311,39 @@ def change_group_member(action, group, userid): ceo_ldap.modify_s(group_dn, mlist) + +### Clubs ### + +def create_club(username, name): + """ + Creates a UNIX user account with options tailored to CSC-hosted clubs. + + Parameters: + username - the desired UNIX username + name - the club name + + Exceptions: + InvalidArgument - on bad account attributes provided + + Returns: the uid number of the new account + + See: create() + """ + + # check username format + if not username or not re.match(cfg['username_regex'], username): + raise InvalidArgument("username", username, "expected format %s" % repr(cfg['username_regex'])) + + args = [ "/usr/bin/addclub", username, name ] + addclub = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = addclub.communicate() + status = addclub.wait() + + if status: + raise ChildFailed("addclub", status, out+err) + + + ### Terms ### def register(userid, term_list): diff --git a/pylib/csc/apps/urwid/groups.py b/pylib/csc/apps/urwid/groups.py index 4acaf43..3b01258 100644 --- a/pylib/csc/apps/urwid/groups.py +++ b/pylib/csc/apps/urwid/groups.py @@ -3,7 +3,7 @@ from csc.apps.urwid.widgets import * from csc.apps.urwid.window import * import csc.apps.urwid.search as search -from csc.adm import accounts, members +from csc.adm import members from csc.common.excep import InvalidArgument def menu_items(items): diff --git a/pylib/csc/apps/urwid/info.py b/pylib/csc/apps/urwid/info.py index 8e0bd76..be15683 100644 --- a/pylib/csc/apps/urwid/info.py +++ b/pylib/csc/apps/urwid/info.py @@ -3,7 +3,7 @@ import urwid from csc.apps.urwid.widgets import * from csc.apps.urwid.window import * -from csc.adm import accounts, members +from csc.adm import members from csc.common.excep import InvalidArgument class InfoPage(WizardPanel): diff --git a/pylib/csc/apps/urwid/main.py b/pylib/csc/apps/urwid/main.py index 43f7ca7..8e116cb 100644 --- a/pylib/csc/apps/urwid/main.py +++ b/pylib/csc/apps/urwid/main.py @@ -10,7 +10,7 @@ import csc.apps.urwid.search as search import csc.apps.urwid.positions as positions import csc.apps.urwid.groups as groups -from csc.adm import accounts, members, terms +from csc.adm import members, terms from csc.common.excep import InvalidArgument ui = urwid.curses_display.Screen() @@ -149,7 +149,6 @@ def manage_positions(data): def run(): members.connect() - accounts.connect() push_window( main_menu(), program_name() ) event_loop( ui ) diff --git a/pylib/csc/apps/urwid/newmember.py b/pylib/csc/apps/urwid/newmember.py index 4706694..37f4c8f 100644 --- a/pylib/csc/apps/urwid/newmember.py +++ b/pylib/csc/apps/urwid/newmember.py @@ -2,7 +2,7 @@ import urwid from csc.apps.urwid.widgets import * from csc.apps.urwid.window import * -from csc.adm import accounts, members, terms +from csc.adm import members, terms from csc.common.excep import InvalidArgument class IntroPage(WizardPanel): @@ -153,23 +153,17 @@ class EndPage(WizardPanel): problem = None try: if self.utype == 'member': - accounts.create_member( self.state['userid'], self.state['password'], self.state['name'], self.state['program'] ) + members.create_member( self.state['userid'], self.state['password'], self.state['name'], self.state['program'] ) members.register( self.state['userid'], terms.current() ) elif self.utype == 'club': - accounts.create_club( self.state['userid'], self.state['name'] ) + members.create_club( self.state['userid'], self.state['name'] ) else: raise Exception("Internal Error") - except accounts.NameConflict, e: + except members.InvalidArgument, e: problem = str(e) - except accounts.NoAvailableIDs, e: + except members.LDAPException, e: problem = str(e) - except accounts.InvalidArgument, e: - problem = str(e) - except accounts.LDAPException, e: - problem = str(e) - except accounts.KrbException, e: - problem = str(e) - except accounts.ChildFailed, e: + except members.ChildFailed, e: problem = str(e) if problem: diff --git a/pylib/csc/apps/urwid/positions.py b/pylib/csc/apps/urwid/positions.py index 285afd3..dded23b 100644 --- a/pylib/csc/apps/urwid/positions.py +++ b/pylib/csc/apps/urwid/positions.py @@ -2,7 +2,7 @@ import urwid from csc.apps.urwid.widgets import * from csc.apps.urwid.window import * -from csc.adm import accounts, members +from csc.adm import members from csc.common.excep import InvalidArgument position_data = [ diff --git a/pylib/csc/apps/urwid/search.py b/pylib/csc/apps/urwid/search.py index a049b82..d4b23c6 100644 --- a/pylib/csc/apps/urwid/search.py +++ b/pylib/csc/apps/urwid/search.py @@ -3,7 +3,7 @@ import urwid from csc.apps.urwid.widgets import * from csc.apps.urwid.window import * -from csc.adm import accounts, members, terms +from csc.adm import members, terms from csc.common.excep import InvalidArgument class TermPage(WizardPanel): diff --git a/pylib/csc/backends/__init__.py b/pylib/csc/backends/__init__.py index e00e35a..f747f24 100644 --- a/pylib/csc/backends/__init__.py +++ b/pylib/csc/backends/__init__.py @@ -3,6 +3,5 @@ Backend Modules This module contains backend interfaces and related modules. - db - CEO database interface for member registrations ldapi - LDAP interface for UNIX account attribute administration """