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.
|
This module provides member and account management modules.
|
||||||
|
|
||||||
members - member registration management functions
|
members - member registration management functions
|
||||||
accounts - account administration functions
|
|
||||||
terms - helper routines for manipulating terms
|
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
|
Future changes to the members database that need to be atomic
|
||||||
must also be moved into this module.
|
must also be moved into this module.
|
||||||
"""
|
"""
|
||||||
import re, ldap
|
import re, subprocess, ldap
|
||||||
from csc.adm import terms
|
|
||||||
from csc.backends import ldapi
|
|
||||||
from csc.common import conf
|
from csc.common import conf
|
||||||
from csc.common.excep import InvalidArgument
|
from csc.common.excep import InvalidArgument
|
||||||
|
from csc.backends import ldapi
|
||||||
|
|
||||||
|
|
||||||
### Configuration ###
|
### Configuration ###
|
||||||
|
|
||||||
CONFIG_FILE = '/etc/csc/members.cf'
|
CONFIG_FILE = '/etc/csc/accounts.cf'
|
||||||
|
|
||||||
cfg = {}
|
cfg = {}
|
||||||
|
|
||||||
def load_configuration():
|
def configure():
|
||||||
"""Load Members Configuration"""
|
"""Load Members Configuration"""
|
||||||
|
|
||||||
string_fields = [ 'realname_regex', 'server_url', 'users_base',
|
string_fields = [ 'member_shell', 'member_home', 'member_desc',
|
||||||
'groups_base', 'sasl_mech', 'sasl_realm', 'admin_bind_keytab',
|
'member_group', 'club_shell', 'club_home', 'club_desc',
|
||||||
'admin_bind_userid' ]
|
'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
|
# read configuration file
|
||||||
cfg_tmp = conf.read(CONFIG_FILE)
|
cfg_tmp = conf.read(CONFIG_FILE)
|
||||||
|
|
||||||
# verify configuration
|
# verify configuration
|
||||||
conf.check_string_fields(CONFIG_FILE, string_fields, cfg_tmp)
|
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
|
# update the current configuration with the loaded values
|
||||||
cfg.update(cfg_tmp)
|
cfg.update(cfg_tmp)
|
||||||
|
@ -43,6 +50,7 @@ def load_configuration():
|
||||||
### Exceptions ###
|
### Exceptions ###
|
||||||
|
|
||||||
ConfigurationException = conf.ConfigurationException
|
ConfigurationException = conf.ConfigurationException
|
||||||
|
LDAPException = ldapi.LDAPException
|
||||||
|
|
||||||
class MemberException(Exception):
|
class MemberException(Exception):
|
||||||
"""Base exception class for member-related errors."""
|
"""Base exception class for member-related errors."""
|
||||||
|
@ -61,6 +69,14 @@ class NoSuchMember(MemberException):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Member not found: %d" % self.memberid
|
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 ###
|
### Connection Management ###
|
||||||
|
@ -71,7 +87,7 @@ ldap_connection = ldapi.LDAPConnection()
|
||||||
def connect():
|
def connect():
|
||||||
"""Connect to LDAP."""
|
"""Connect to LDAP."""
|
||||||
|
|
||||||
load_configuration()
|
configure()
|
||||||
ldap_connection.connect_sasl(cfg['server_url'], cfg['sasl_mech'],
|
ldap_connection.connect_sasl(cfg['server_url'], cfg['sasl_mech'],
|
||||||
cfg['sasl_realm'], cfg['admin_bind_userid'],
|
cfg['sasl_realm'], cfg['admin_bind_userid'],
|
||||||
('keytab', cfg['admin_bind_keytab']), cfg['users_base'],
|
('keytab', cfg['admin_bind_keytab']), cfg['users_base'],
|
||||||
|
@ -92,6 +108,45 @@ def connected():
|
||||||
|
|
||||||
### Members ###
|
### 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):
|
def get(userid):
|
||||||
"""
|
"""
|
||||||
Look up attributes of a member by 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)
|
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 ###
|
### Terms ###
|
||||||
|
|
||||||
def register(userid, term_list):
|
def register(userid, term_list):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from csc.apps.urwid.widgets import *
|
||||||
from csc.apps.urwid.window import *
|
from csc.apps.urwid.window import *
|
||||||
import csc.apps.urwid.search as search
|
import csc.apps.urwid.search as search
|
||||||
|
|
||||||
from csc.adm import accounts, members
|
from csc.adm import members
|
||||||
from csc.common.excep import InvalidArgument
|
from csc.common.excep import InvalidArgument
|
||||||
|
|
||||||
def menu_items(items):
|
def menu_items(items):
|
||||||
|
|
|
@ -3,7 +3,7 @@ import urwid
|
||||||
from csc.apps.urwid.widgets import *
|
from csc.apps.urwid.widgets import *
|
||||||
from csc.apps.urwid.window import *
|
from csc.apps.urwid.window import *
|
||||||
|
|
||||||
from csc.adm import accounts, members
|
from csc.adm import members
|
||||||
from csc.common.excep import InvalidArgument
|
from csc.common.excep import InvalidArgument
|
||||||
|
|
||||||
class InfoPage(WizardPanel):
|
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.positions as positions
|
||||||
import csc.apps.urwid.groups as groups
|
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
|
from csc.common.excep import InvalidArgument
|
||||||
|
|
||||||
ui = urwid.curses_display.Screen()
|
ui = urwid.curses_display.Screen()
|
||||||
|
@ -149,7 +149,6 @@ def manage_positions(data):
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
members.connect()
|
members.connect()
|
||||||
accounts.connect()
|
|
||||||
|
|
||||||
push_window( main_menu(), program_name() )
|
push_window( main_menu(), program_name() )
|
||||||
event_loop( ui )
|
event_loop( ui )
|
||||||
|
|
|
@ -2,7 +2,7 @@ import urwid
|
||||||
from csc.apps.urwid.widgets import *
|
from csc.apps.urwid.widgets import *
|
||||||
from csc.apps.urwid.window 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
|
from csc.common.excep import InvalidArgument
|
||||||
|
|
||||||
class IntroPage(WizardPanel):
|
class IntroPage(WizardPanel):
|
||||||
|
@ -153,23 +153,17 @@ class EndPage(WizardPanel):
|
||||||
problem = None
|
problem = None
|
||||||
try:
|
try:
|
||||||
if self.utype == 'member':
|
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() )
|
members.register( self.state['userid'], terms.current() )
|
||||||
elif self.utype == 'club':
|
elif self.utype == 'club':
|
||||||
accounts.create_club( self.state['userid'], self.state['name'] )
|
members.create_club( self.state['userid'], self.state['name'] )
|
||||||
else:
|
else:
|
||||||
raise Exception("Internal Error")
|
raise Exception("Internal Error")
|
||||||
except accounts.NameConflict, e:
|
except members.InvalidArgument, e:
|
||||||
problem = str(e)
|
problem = str(e)
|
||||||
except accounts.NoAvailableIDs, e:
|
except members.LDAPException, e:
|
||||||
problem = str(e)
|
problem = str(e)
|
||||||
except accounts.InvalidArgument, e:
|
except members.ChildFailed, e:
|
||||||
problem = str(e)
|
|
||||||
except accounts.LDAPException, e:
|
|
||||||
problem = str(e)
|
|
||||||
except accounts.KrbException, e:
|
|
||||||
problem = str(e)
|
|
||||||
except accounts.ChildFailed, e:
|
|
||||||
problem = str(e)
|
problem = str(e)
|
||||||
|
|
||||||
if problem:
|
if problem:
|
||||||
|
|
|
@ -2,7 +2,7 @@ import urwid
|
||||||
from csc.apps.urwid.widgets import *
|
from csc.apps.urwid.widgets import *
|
||||||
from csc.apps.urwid.window import *
|
from csc.apps.urwid.window import *
|
||||||
|
|
||||||
from csc.adm import accounts, members
|
from csc.adm import members
|
||||||
from csc.common.excep import InvalidArgument
|
from csc.common.excep import InvalidArgument
|
||||||
|
|
||||||
position_data = [
|
position_data = [
|
||||||
|
|
|
@ -3,7 +3,7 @@ import urwid
|
||||||
from csc.apps.urwid.widgets import *
|
from csc.apps.urwid.widgets import *
|
||||||
from csc.apps.urwid.window 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
|
from csc.common.excep import InvalidArgument
|
||||||
|
|
||||||
class TermPage(WizardPanel):
|
class TermPage(WizardPanel):
|
||||||
|
|
|
@ -3,6 +3,5 @@ Backend Modules
|
||||||
|
|
||||||
This module contains backend interfaces and related modules.
|
This module contains backend interfaces and related modules.
|
||||||
|
|
||||||
db - CEO database interface for member registrations
|
|
||||||
ldapi - LDAP interface for UNIX account attribute administration
|
ldapi - LDAP interface for UNIX account attribute administration
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue