Merge accounts and members
This really only moves two functions: create_member and create_club. Nothing else is left in accounts. Eventually, it might make sense to separate it out again in a more sane way (e.g. they should share the connection somehow).
This commit is contained in:
parent
8782e58118
commit
5bca2288af
|
@ -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 = "^[^,:=]*$"
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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", "<hidden>", "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)
|
|
@ -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", "<hidden>", "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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue