Complete group and position management

This commit is contained in:
David Bartley 2007-11-15 05:28:58 -05:00
parent 97ac7fb7bd
commit 95019e376a
8 changed files with 296 additions and 15 deletions

View File

@ -12,11 +12,20 @@ attributetype ( 1.3.6.1.4.1.27934.1.1.3 NAME 'studentid'
EQUALITY caseIgnoreIA5Match EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{8} SINGLE-VALUE ) SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{8} SINGLE-VALUE )
attributetype ( 1.3.6.1.4.1.27934.1.1.4 NAME 'position'
EQUALITY caseIgnoreIA5Match
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{32} )
objectclass ( 1.3.6.1.4.1.27934.1.2.1 NAME 'member' objectclass ( 1.3.6.1.4.1.27934.1.2.1 NAME 'member'
SUP top AUXILIARY SUP top AUXILIARY
MUST ( cn $ uid ) MUST ( cn $ uid )
MAY ( studentid $ program $ term $ description ) ) MAY ( studentid $ program $ term $ description $ position ) )
objectclass ( 1.3.6.1.4.1.27934.1.2.2 NAME 'club' objectclass ( 1.3.6.1.4.1.27934.1.2.2 NAME 'club'
SUP top AUXILIARY SUP top AUXILIARY
MUST ( cn $ uid ) ) MUST ( cn $ uid ) )
objectclass ( 1.3.6.1.4.1.27934.1.2.3 NAME 'group'
SUP top STRUCTURAL
MUST ( cn )
MAY ( uniqueMember ) )

View File

@ -9,7 +9,7 @@ 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 import re, ldap
from csc.adm import terms from csc.adm import terms
from csc.backends import ldapi from csc.backends import ldapi
from csc.common import conf from csc.common import conf
@ -220,6 +220,72 @@ def list_group(group):
return {} return {}
def list_positions():
"""
Build a list of positions
Returns: a list of positions and who holds them
Example: list_positions(): -> {
'president': { 'mspang': { 'cn': 'Michael Spang', ... } } ],
...
]
"""
ceo_ldap = ldap_connection.ldap
user_base = ldap_connection.user_base
escape = ldap_connection.escape
if not ldap_connection.connected(): ldap_connection.connect()
members = ceo_ldap.search_s(user_base, ldap.SCOPE_SUBTREE, '(position=*)')
positions = {}
for (_, member) in members:
for position in member['position']:
if not position in positions:
positions[position] = {}
positions[position][member['uid'][0]] = member
return positions
def set_position(position, members):
"""
Sets a position
Parameters:
position - the position to set
members - an array of members that hold the position
Example: set_position('president', ['dtbartle'])
"""
ceo_ldap = ldap_connection.ldap
user_base = ldap_connection.user_base
escape = ldap_connection.escape
res = ceo_ldap.search_s(user_base, ldap.SCOPE_SUBTREE,
'(&(objectClass=member)(position=%s))' % escape(position))
old = set([ member['uid'][0] for (_, member) in res ])
new = set(members)
mods = {
'del': set(old) - set(new),
'add': set(new) - set(old),
}
if len(mods['del']) == 0 and len(mods['add']) == 0:
return
for type in ['del', 'add']:
for userid in mods[type]:
dn = 'uid=%s,%s' % (escape(userid), user_base)
entry1 = {'position' : [position]}
entry2 = {} #{'position' : []}
entry = ()
if type == 'del':
entry = (entry1, entry2)
elif type == 'add':
entry = (entry2, entry1)
mlist = ldap_connection.make_modlist(entry[0], entry[1])
ceo_ldap.modify_s(dn, mlist)
def delete(userid): def delete(userid):
""" """
Erase all records of a member. Erase all records of a member.
@ -248,6 +314,27 @@ def delete(userid):
return member return member
def change_group_member(action, group, userid):
ceo_ldap = ldap_connection.ldap
user_base = ldap_connection.user_base
group_base = ldap_connection.group_base
escape = ldap_connection.escape
user_dn = 'uid=%s,%s' % (escape(userid), user_base)
group_dn = 'cn=%s,%s' % (escape(group), group_base)
entry1 = {'uniqueMember' : []}
entry2 = {'uniqueMember' : [user_dn]}
entry = []
if action == 'add' or action == 'insert':
entry = (entry1, entry2)
elif action == 'remove' or action == 'delete':
entry = (entry2, entry1)
else:
raise InvalidArgument("action", action, "invalid action")
mlist = ldap_connection.make_modlist(entry[0], entry[1])
ceo_ldap.modify_s(group_dn, mlist)
### Term Table ### ### Term Table ###

View File

@ -0,0 +1,64 @@
import urwid
from csc.apps.urwid.widgets import *
from csc.apps.urwid.window import *
from csc.adm import accounts, members
from csc.common.excep import InvalidArgument
class ChangeMember(WizardPanel):
def __init__(self, state, data):
state['data'] = data
WizardPanel.__init__(self, state)
def init_widgets(self):
self.userid = WordEdit("Username: ")
data = self.state['data']
self.widgets = [
urwid.Text( "%s %s Member" % (data['type'], data['name']) ),
urwid.Divider(),
self.userid,
]
def check(self):
self.state['userid'] = self.userid.get_edit_text()
if self.state['userid']:
if not members.connected(): members.connect()
self.state['member'] = members.get(self.userid.get_edit_text())
if not self.state['member']:
set_status("Member not found")
self.focus_widget(self.userid)
return True
clear_status()
class EndPage(WizardPanel):
def init_widgets(self):
self.headtext = urwid.Text("")
self.midtext = urwid.Text("")
self.widgets = [
self.headtext,
urwid.Divider(),
self.midtext,
]
def focusable(self):
return False
def check(self):
pop_window()
def activate(self):
data = self.state['data']
type = data['type'].lower()
failed = []
for group in data['groups']:
try:
members.change_group_member(type, group, self.state['userid'])
except:
failed.append(group)
if len(failed) == 0:
self.headtext.set_text("%s succeeded" % data['type'])
self.midtext.set_text("Congratulations, the group modification "
"has succeeded.")
else:
self.headtext.set_text("%s partially succeeded" % data['type'])
self.midtext.set_text("Failed to %s member to %s for the "
"following groups: %s. This may indicate an attempt to add a "
"duplicate group member or to delete a non-present group "
"member." % (data['type'].lower(), data['name'],
', '.join(failed)))

View File

@ -21,7 +21,8 @@ class LdapFilter:
search = self.escape(self.widget.get_edit_text(self)) search = self.escape(self.widget.get_edit_text(self))
filter = '(%s=%s)' % (attr, search) filter = '(%s=%s)' % (attr, search)
try: try:
matches = self.ldap.search_s(self.base, ldap.SCOPE_SUBTREE, filter) matches = self.ldap.search_s(self.base,
ldap.SCOPE_SUBTREE, filter)
if len(matches) > 0: if len(matches) > 0:
(_, attrs) = matches[0] (_, attrs) = matches[0]
for (k, v) in self.map.items(): for (k, v) in self.map.items():

View File

@ -7,6 +7,8 @@ import csc.apps.urwid.newmember as newmember
import csc.apps.urwid.renew as renew import csc.apps.urwid.renew as renew
import csc.apps.urwid.info as info import csc.apps.urwid.info as info
import csc.apps.urwid.search as search 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 accounts, members, terms
from csc.common.excep import InvalidArgument from csc.common.excep import InvalidArgument
@ -49,12 +51,14 @@ def program_name():
office_data = { office_data = {
"name" : "Office Staff", "name" : "Office Staff",
"group" : "office" "group" : "office",
"groups" : [ "office", "cdrom", "audio", "video", "www" ],
} }
syscom_data = { syscom_data = {
"name" : "Systems Committee", "name" : "Systems Committee",
"group" : "syscom" "group" : "syscom",
"groups" : [ "office", "staff", "adm", "src" ],
} }
def menu_items(items): def menu_items(items):
@ -67,6 +71,7 @@ def main_menu():
("Create Club Account", new_club, None), ("Create Club Account", new_club, None),
("Display Member", display_member, None), ("Display Member", display_member, None),
("Search", search_members, None), ("Search", search_members, None),
("Manage Positions", manage_positions, None),
("Manage Office Staff", group_members, office_data), ("Manage Office Staff", group_members, office_data),
("Manage Systems Committee", group_members, syscom_data), ("Manage Systems Committee", group_members, syscom_data),
("Exit", raise_abort, None), ("Exit", raise_abort, None),
@ -100,7 +105,7 @@ def new_club(*args, **kwargs):
newmember.ClubIntroPage, newmember.ClubIntroPage,
newmember.ClubInfoPage, newmember.ClubInfoPage,
(newmember.EndPage, "club"), (newmember.EndPage, "club"),
], (60,15)) ], (60, 15))
def renew_member(*args, **kwargs): def renew_member(*args, **kwargs):
push_wizard("Renew Membership", [ push_wizard("Renew Membership", [
@ -137,10 +142,23 @@ def search_term(data):
def search_group(data): def search_group(data):
push_wizard("By Group", [ search.GroupPage ]) push_wizard("By Group", [ search.GroupPage ])
def manage_positions(data):
push_wizard("Manage Positions", [
positions.IntroPage,
positions.InfoPage,
positions.EndPage,
], (50, 15))
def group_members(data): def group_members(data):
add_data = data.copy()
add_data['type'] = 'Add'
remove_data = data.copy()
remove_data['type'] = 'Remove'
menu = [ menu = [
("Add %s member" % data["name"].lower(), add_group_member, data), ("Add %s member" % data["name"].lower(),
("Remove %s member" % data["name"].lower(), remove_group_member, data), change_group_member, add_data),
("Remove %s member" % data["name"].lower(),
change_group_member, remove_data),
("List %s members" % data["name"].lower(), list_group_members, data), ("List %s members" % data["name"].lower(), list_group_members, data),
("Back", raise_back, None), ("Back", raise_back, None),
] ]
@ -148,11 +166,11 @@ def group_members(data):
listbox = urwid.ListBox( menu_items( menu ) ) listbox = urwid.ListBox( menu_items( menu ) )
push_window(listbox, "Manage %s" % data["name"]) push_window(listbox, "Manage %s" % data["name"])
def add_group_member(data): def change_group_member(data):
pass push_wizard("%s %s Member" % (data["type"], data["name"]), [
(groups.ChangeMember, data),
def remove_group_member(data): groups.EndPage,
pass ])
def list_group_members(data): def list_group_members(data):
if not members.connected(): members.connect() if not members.connected(): members.connect()

View File

@ -58,7 +58,7 @@ class InfoPage(WizardPanel):
self.state['name'] = self.name.get_edit_text() self.state['name'] = self.name.get_edit_text()
self.state['program'] = self.program.get_edit_text() self.state['program'] = self.program.get_edit_text()
if len( self.state['userid'] ) < 4: if len( self.state['userid'] ) < 3:
self.focus_widget( self.userid ) self.focus_widget( self.userid )
set_status("Username is too short") set_status("Username is too short")
return True return True
@ -188,5 +188,7 @@ class EndPage(WizardPanel):
else: else:
self.headtext.set_text("User Added") self.headtext.set_text("User Added")
self.midtext.set_text("Congratulations, %s has been added " self.midtext.set_text("Congratulations, %s has been added "
"successfully. Please run 'addhomedir %s'." "successfully. Please run 'addhomedir %s'. "
"You should also rebuild the website in order to update the "
"memberlist."
% (self.state['userid'], self.state['userid'])) % (self.state['userid'], self.state['userid']))

View File

@ -0,0 +1,83 @@
import urwid
from csc.apps.urwid.widgets import *
from csc.apps.urwid.window import *
from csc.adm import accounts, members
from csc.common.excep import InvalidArgument
position_data = [
('president', 'President'),
('vice-president', 'Vice-president'),
('treasurer', 'Treasurer'),
('secretary', 'Secretary'),
('sysadmin', 'System Administrator'),
('librarian', 'Librarian'),
('imapd', 'Imapd'),
('webmaster', 'Web Master'),
]
class IntroPage(WizardPanel):
def init_widgets(self):
self.widgets = [
urwid.Text( "Managing Positions" ),
urwid.Divider(),
urwid.Text( "Enter a username for each position. If a position is "
"held by multiple people, enter a comma-separated "
"list of usernames. If a position is held by nobody "
"leave the username blank." ),
]
def focusable(self):
return False
class InfoPage(WizardPanel):
def init_widgets(self):
if not members.connected(): members.connect()
self.widgets = [
urwid.Text( "Positions" ),
urwid.Divider(),
]
positions = members.list_positions()
self.position_widgets = {}
for (position, text) in position_data:
widget = WordEdit("%s: " % text)
if position in positions:
widget.set_edit_text(','.join(positions[position].keys()))
else:
widget.set_edit_text('')
self.position_widgets[position] = widget
self.widgets.append(widget)
def parse(self, entry):
if len(entry) == 0:
return []
return entry.split(',')
def check(self):
self.state['positions'] = {}
for (position, widget) in self.position_widgets.iteritems():
self.state['positions'][position] = \
self.parse(widget.get_edit_text())
clear_status()
class EndPage(WizardPanel):
def init_widgets(self):
old = members.list_positions()
self.headtext = urwid.Text("")
self.midtext = urwid.Text("")
self.widgets = [
self.headtext,
urwid.Divider(),
self.midtext,
]
def focusable(self):
return False
def activate(self):
for (position, info) in self.state['positions'].iteritems():
members.set_position(position, info)
self.headtext.set_text("Positions Updated")
self.midtext.set_text("Congratulations, positions have been updated. "
"You should rebuild the website in order to update the Positions "
"page.")
def check(self):
pop_window()

View File

@ -641,6 +641,23 @@ class LDAPConnection(object):
return gids return gids
def make_modlist(self, old, new):
keys = set(old.keys()).union(set(new))
mlist = []
for key in keys:
if key in old and not key in new:
mlist.append((ldap.MOD_DELETE, key, list(set(old[key]))))
elif key in new and not key in old:
mlist.append((ldap.MOD_ADD, key, list(set(new[key]))))
else:
to_add = list(set(new[key]) - set(old[key]))
if len(to_add) > 0:
mlist.append((ldap.MOD_ADD, key, to_add))
to_del = list(set(old[key]) - set(new[key]))
if len(to_del) > 0:
mlist.append((ldap.MOD_DELETE, key, to_del))
return mlist
### Tests ### ### Tests ###