Remove old GUI

This commit is contained in:
Michael Spang 2007-12-04 21:44:16 -05:00
parent 1aa2a15d70
commit 28df5a8dc4
4 changed files with 0 additions and 981 deletions

View File

@ -1,31 +0,0 @@
#!/usr/bin/python2.4 --
"""CEO SUID Python Wrapper Script"""
import os, sys
safe_environment = ['LOGNAME', 'USERNAME', 'USER', 'HOME', 'TERM', 'LANG'
'LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MESSAGES', 'LC_MONETARY',
'LC_NUMERIC', 'LC_TIME', 'UID', 'GID', 'SSH_CONNECTION', 'SSH_AUTH_SOCK',
'SSH_CLIENT']
for key in os.environ.keys():
if key not in safe_environment:
del os.environ[key]
os.environ['LESSSECURE'] = '1'
os.environ['PATH'] = '/usr/sbin:/usr/bin:/sbin:/bin'
for pathent in sys.path[:]:
if not pathent.find('/usr') == 0:
sys.path.remove(pathent)
euid = os.geteuid()
egid = os.getegid()
try:
os.setreuid(euid, euid)
os.setregid(egid, egid)
except OSError, e:
print str(e)
sys.exit(1)
import csc.apps.legacy.main
csc.apps.legacy.main.run()

View File

@ -1,8 +0,0 @@
"""
Legacy User Interface
This module contains the legacy CEO user interface and related modules.
main - all of the main UI logic
helpers - user interface library routines
"""

View File

@ -1,411 +0,0 @@
"""
Helpers for legacy User Interface
This module contains numerous functions that are designed to immitate
the look and behavior of the previous CEO. Included is code for various
curses-based UI widgets that were provided by Perl 5's Curses and
Curses::Widgets libraries.
Though attempts have been made to keep the UI [bug-]compatible with
the previous system, some compromises have been made. For example,
the input and textboxes draw 'OK' and 'Cancel' buttons where the old
CEO had them, but they are fake. That is, the buttons in the old
CEO were selectable but non-operational, but in the new CEO they are
not even selectable.
"""
import curses.ascii
# key constants not defined in CURSES
KEY_RETURN = ord('\n')
KEY_ESCAPE = 27
KEY_EOT = 4
def center(parent_dim, child_dim):
"""Helper for centering a length in a larget length."""
return (parent_dim-child_dim)/2
def read_input(wnd, offy, offx, width, maxlen, echo=True):
"""
Read user input within a confined region of a window.
Basic line-editing is supported:
LEFT, RIGHT, HOME, and END move around.
BACKSPACE and DEL remove characters.
INSERT switches between insert and overwrite mode.
ESC and C-d abort input.
RETURN completes input.
Parameters:
wnd - parent window for region
offy - the vertical offset for the beginning of the input region
offx - the horizontal offset for the beginning of the input region
width - the width of the region
maxlen - greatest number of characters to read (0 for no limit)
echo - boolean: whether to display typed characters
Returns: the string, or None when the user aborts.
"""
# turn on cursor
try:
curses.curs_set(1)
except curses.error:
pass
# set keypad mode to allow UP, DOWN, etc
wnd.keypad(1)
# the input string
inputbuf = ""
# offset of cursor in input
# i.e. the next operation is applied at input[inputoff]
inputoff = 0
# display offset (for scrolling)
# i.e. the first character in the region is input[displayoff]
displayoff = 0
# insert mode (True) or overwrite mode (False)
insert = True
while True:
# echo mode, display the string
if echo:
# discard characters before displayoff,
# as the window may be scrolled to the right
substring = inputbuf[displayoff:]
# pad the string with zeroes to overwrite stale characters
substring = substring + " " * (width - len(substring))
# display the substring
wnd.addnstr(offy, offx, substring, width)
# await input
key = wnd.getch(offy, offx + inputoff - displayoff)
# not echo mode, don't display the string
else:
# await input at arbitrary location
key = wnd.getch(offy, offx)
# enter returns input
if key == KEY_RETURN:
return inputbuf
# escape aborts input
elif key == KEY_ESCAPE:
return None
# EOT (C-d) aborts if there is no input
elif key == KEY_EOT:
if len(inputbuf) == 0:
return None
# backspace removes the previous character
elif key == curses.KEY_BACKSPACE:
if inputoff > 0:
# remove the character immediately before the input offset
inputbuf = inputbuf[0:inputoff-1] + inputbuf[inputoff:]
inputoff -= 1
# move either the cursor or entire line of text left
if displayoff > 0:
displayoff -= 1
# delete removes the current character
elif key == curses.KEY_DC:
if inputoff < len(input):
# remove the character at the input offset
inputbuf = inputbuf[0:inputoff] + inputbuf[inputoff+1:]
# left moves the cursor one character left
elif key == curses.KEY_LEFT:
if inputoff > 0:
# move the cursor to the left
inputoff -= 1
# scroll left if necessary
if inputoff < displayoff:
displayoff -= 1
# right moves the cursor one character right
elif key == curses.KEY_RIGHT:
if inputoff < len(inputbuf):
# move the cursor to the right
inputoff += 1
# scroll right if necessary
if displayoff - inputoff == width:
displayoff += 1
# home moves the cursor to the first character
elif key == curses.KEY_HOME:
inputoff = 0
displayoff = 0
# end moves the cursor past the last character
elif key == curses.KEY_END:
inputoff = len(inputbuf)
displayoff = len(inputbuf) - width + 1
# insert toggles insert/overwrite mode
elif key == curses.KEY_IC:
insert = not insert
# other (printable) characters are added to the input string
elif curses.ascii.isprint(key):
if len(inputbuf) < maxlen or maxlen == 0:
# insert mode: insert before current offset
if insert:
inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff:]
# overwrite mode: replace current offset
else:
inputbuf = inputbuf[0:inputoff] + chr(key) + inputbuf[inputoff+1:]
# increment the input offset
inputoff += 1
# scroll right if necessary
if inputoff - displayoff == width:
displayoff += 1
def inputbox(wnd, prompt, field_width, echo=True):
"""Display a window for user input."""
wnd_height, wnd_width = wnd.getmaxyx()
height, width = 12, field_width + 7
# draw a window for the dialog
childy, childx = center(wnd_height-1, height)+1, center(wnd_width, width)
child_wnd = wnd.subwin(height, width, childy, childx)
child_wnd.clear()
child_wnd.border()
# draw another window for the text box
texty, textx = center(height-1, 3)+1, center(width-1, width-5)+1
textheight, textwidth = 3, width-5
text_wnd = child_wnd.derwin(textheight, textwidth, texty, textx)
text_wnd.clear()
text_wnd.border()
# draw the prompt
prompty, promptx = 2, 3
child_wnd.addnstr(prompty, promptx, prompt, width-2)
# draw the fake buttons
fakey, fakex = 9, width - 19
child_wnd.addstr(fakey, fakex, "< OK > < Cancel >")
child_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE)
child_wnd.addch(fakey, fakex+9, "C", curses.A_UNDERLINE)
# update the screen
child_wnd.noutrefresh()
text_wnd.noutrefresh()
curses.doupdate()
# read an input string within the field region of text_wnd
inputy, inputx, inputwidth = 1, 1, textwidth - 2
inputbuf = read_input(text_wnd, inputy, inputx, inputwidth, 0, echo)
# erase the window
child_wnd.erase()
child_wnd.refresh()
return inputbuf
def line_wrap(line, width):
"""Wrap a string to a certain width (returns a list of strings)."""
wrapped_lines = []
tokens = line.split(" ")
tokens.reverse()
tmp = tokens.pop()
if len(tmp) > width:
wrapped_lines.append(tmp[0:width])
tmp = tmp[width:]
while len(tokens) > 0:
token = tokens.pop()
if len(tmp) + len(token) + 1 <= width:
tmp += " " + token
elif len(token) > width:
tmp += " " + token[0:width-len(tmp)-1]
tokens.push(token[width-len(tmp)-1:])
else:
wrapped_lines.append(tmp)
tmp = token
wrapped_lines.append(tmp)
return wrapped_lines
def msgbox(wnd, msg, title="Message"):
"""Display a message in a window."""
# split message into a list of lines
lines = msg.split("\n")
# determine the dimensions of the method
message_height = len(lines)
message_width = 0
for line in lines:
if len(line) > message_width:
message_width = len(line)
# ensure the window fits the title
if len(title) > message_width:
message_width = len(title)
# maximum message width
parent_height, parent_width = wnd.getmaxyx()
max_message_width = parent_width - 8
# line-wrap if necessary
if message_width > max_message_width:
newlines = []
for line in lines:
for newline in line_wrap(line, max_message_width):
newlines.append(newline)
lines = newlines
message_width = max_message_width
message_height = len(lines)
# random padding that perl's curses adds
pad_width = 2
# create the outer window
outer_height, outer_width = message_height + 8, message_width + pad_width + 6
outer_y, outer_x = center(parent_height+1, outer_height)-1, center(parent_width, outer_width)
outer_wnd = wnd.derwin(outer_height, outer_width, outer_y, outer_x)
outer_wnd.erase()
outer_wnd.border()
# create the inner window
inner_height, inner_width = message_height + 2, message_width + pad_width + 2
inner_y, inner_x = 2, center(outer_width, inner_width)
inner_wnd = outer_wnd.derwin(inner_height, inner_width, inner_y, inner_x)
inner_wnd.border()
# display the title
outer_wnd.addstr(0, 1, " " + title + " ", curses.A_REVERSE | curses.A_BOLD)
# display the message
for i in xrange(len(lines)):
inner_wnd.addnstr(i+1, 1, lines[i], message_width)
# draw a solid block at the end of the first few lines
if i < len(lines) - 1:
inner_wnd.addch(i+1, inner_width-1, ' ', curses.A_REVERSE)
# display the fake OK button
fakey, fakex = outer_height - 3, outer_width - 8
outer_wnd.addstr(fakey, fakex, "< OK >", curses.A_REVERSE)
outer_wnd.addch(fakey, fakex+2, "O", curses.A_UNDERLINE | curses.A_REVERSE)
# update display
outer_wnd.noutrefresh()
inner_wnd.noutrefresh()
curses.doupdate()
# read a RETURN or ESC before returning
curses.curs_set(0)
outer_wnd.keypad(1)
while True:
key = outer_wnd.getch(0, 0)
if key == KEY_RETURN or key == KEY_ESCAPE:
break
# clear the window
outer_wnd.erase()
outer_wnd.refresh()
def menu(wnd, offy, offx, width, options, _acquire_wnd=None):
"""
Draw a menu and wait for a selection.
Parameters:
wnd - parent window
offy - vertical offset for menu region
offx - horizontal offset for menu region
width - width of menu region
options - a list of selections
_acquire_wnd - hack to support resize: must be a function callback
that returns new values for wnd, offy, offx, height,
width. Unused if None.
Returns: index into options that was selected
"""
# the currently selected option
selected = 0
while True:
# disable cursor
curses.curs_set(0)
# hack to support resize: recreate the
# parent window every iteration
if _acquire_wnd:
wnd, offy, offx, height, width = _acquire_wnd()
# keypad mode so getch() works with up, down
wnd.keypad(1)
# display the menu
for i in xrange(len(options)):
text, callback = options[i]
text = text + " " * (width - len(text))
# the selected option is displayed in reverse video
if i == selected:
wnd.addstr(i+offy, offx, text, curses.A_REVERSE)
else:
wnd.addstr(i+offy, offx, text)
# display the member
wnd.refresh()
# read one keypress
keypress = wnd.getch()
# UP moves to the previous option
if (keypress == curses.KEY_UP or keypress == ord('k')) and selected > 0:
selected = (selected - 1)
# DOWN moves to the next option
elif (keypress == curses.KEY_DOWN or keypress == ord('j')) and selected < len(options) - 1:
selected = (selected + 1)
# RETURN runs the callback for the selected option
elif keypress == KEY_RETURN:
text, callback = options[selected]
# highlight the selected option
text = text + " " * (width - len(text))
wnd.addstr(selected+offy, offx, text, curses.A_REVERSE | curses.A_BOLD)
wnd.refresh()
# execute the selected option
if callback(wnd): # success
break
def reset():
"""Reset the terminal and clear the screen."""
reset = curses.tigetstr('rs1')
if not reset: reset = '\x1bc'
curses.putp(reset)

View File

@ -1,531 +0,0 @@
"""
CEO-like Frontend
This frontend aims to be compatible in both look and function with the
curses UI of CEO.
Some small improvements have been made, such as not echoing passwords and
aborting when nothing is typed into the first input box during an operation.
This frontend is poorly documented, deprecated, and undoubtedly full of bugs.
"""
import curses.ascii, re, os
from helpers import menu, inputbox, msgbox, reset
from csc.adm import accounts, members, terms
from csc.common.excep import InvalidArgument
# color of the ceo border
BORDER_COLOR = curses.COLOR_RED
def read_uid(wnd):
"""Read a username."""
prompt = 'Username:'
return inputbox(wnd, prompt, 36)
def read_member(wnd):
"""Looks up a member."""
# connect the members module to its backend if necessary
if not members.connected(): members.connect()
uid = read_uid(wnd)
if not uid or uid.lower() == 'exit':
return
member = members.get(uid)
if not member:
msgbox(wnd, "Invalid username: %s" % uid)
return
# display user
display_member_details(wnd, member)
return member
def action_library(wnd):
"""Display a link to the library."""
msgbox(wnd, "Please visit library.csclub.uwaterloo.ca")
def action_new_member(wnd):
"""Interactively add a new member."""
userid, program = '', ''
msgbox(wnd, "Membership is $2.00 CDN. Please ensure\n"
"the money is desposited in the safe\n"
"before continuing.")
# read the name
prompt = "New member's full name: "
realname = inputbox(wnd, prompt, 30)
# abort if no name is entered
if not realname or realname.lower() == 'exit':
return False
# read the program of study
prompt = "New member's program of study:"
program = inputbox(wnd, prompt, 30)
# abort if exit is entered
if program is None or program.lower() == 'exit':
return False
# read user id
prompt = "New member's UWdir username:"
while userid == '':
userid = inputbox(wnd, prompt, 30)
# user abort
if userid is None or userid.lower() == 'exit':
return False
# connect the members module to its backend if necessary
if not members.connected(): members.connect()
# attempt to create the member
try:
members.new(userid, realname, program)
msgbox(wnd, "Success! Your username is %s. You are now registered\n"
% userid + "for the " + terms.current() + " term.")
except members.InvalidRealName:
msgbox(wnd, 'Invalid real name: "%s"' % realname)
return False
except InvalidArgument, e:
if e.argname == 'uid' and e.explanation == 'duplicate uid':
msgbox(wnd, 'A member with this user ID exists.')
return False
else:
raise
def action_term_register(wnd):
"""Interactively register a member for a term."""
term = ''
member = read_member(wnd)
if not member:
return False
uid = member['uid'][0]
# verify member
prompt = "Is this the correct member?"
answer = None
while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
answer = inputbox(wnd, prompt, 28)
# user abort
if answer == "exit":
return False
# read the term
prompt = "Which term to register for ([wsf]20nn):"
while not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit':
term = inputbox(wnd, prompt, 41)
# abort when exit is entered
if term.lower() == 'exit':
return False
# already registered?
if members.registered(uid, term):
msgbox(wnd, "You are already registered for term " + term)
return False
try:
# attempt to register
members.register(uid, term)
# display success message
msgbox(wnd, "You are now registered for term " + term)
except members.InvalidTerm:
msgbox(wnd, "Term is not valid: %s" % term)
return False
def action_term_register_multiple(wnd):
"""Interactively register a member for multiple terms."""
base, num = '', None
member = read_member(wnd)
if not member:
return False
uid = member['uid'][0]
# verify member
prompt = "Is this the correct member?"
answer = None
while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
answer = inputbox(wnd, prompt, 28)
# user abort
if answer == "exit":
return False
# read the base
prompt = "Which term to start registering ([fws]20nn):"
while not re.match('^[wsf][0-9]{4}$', base) and not base == 'exit':
base = inputbox(wnd, prompt, 41)
# abort when exit is entered
if base.lower() == 'exit':
return False
# read number of terms
prompt = 'How many terms?'
while not num or not re.match('^[0-9]*$', num):
num = inputbox(wnd, prompt, 36)
num = int(num)
# any terms in the range?
if num < 1:
msgbox(wnd, "No terms to register.")
return False
# compile a list to register
term_list = terms.interval(base, num)
# already registered?
for term in term_list:
if members.registered(uid, term):
msgbox(wnd, "You are already registered for term " + term)
return False
try:
# attempt to register all terms
members.register(uid, term_list)
# display success message [sic]
msgbox(wnd, "Your are now registered for terms: " + ", ".join(term_list))
except members.InvalidTerm:
msgbox(wnd, "Invalid term entered.")
return False
def input_password(wnd):
# password input loop
password = "password"
check = "check"
while password != check:
# read password
prompt = "User password:"
password = None
while not password:
password = inputbox(wnd, prompt, 18, False)
# read another password
prompt = "Enter the password again:"
check = None
while not check:
check = inputbox(wnd, prompt, 27, False)
return password
def action_create_account(wnd):
"""Interactively create an account for a member."""
member = read_member(wnd)
if not member:
return False
# member already has an account?
if not accounts.connected(): accounts.connect()
if 'posixAccount' in member['objectClass']:
msgbox(wnd, "Account already exists.")
return False
# verify member
prompt = "Is this the correct member?"
answer = None
while answer != "yes" and answer != "y" and answer != "n" and answer != "no" and answer != "exit":
answer = inputbox(wnd, prompt, 28)
# user abort
if answer == "exit":
return False
# incorrect member; abort
if answer == "no" or answer == "n":
msgbox(wnd, "I suggest searching for the member by userid or name from the main menu.")
return False
msgbox(wnd, "Ensure the member has signed the machine\n"
"usage policy. Accounts of users who have\n"
"not signed will be suspended if discovered.")
# read password
password = input_password(wnd)
# create the UNIX account
try:
if not accounts.connected(): accounts.connect()
accounts.create_member(member['uid'][0], password, member['cn'][0])
except accounts.NameConflict, e:
msgbox(wnd, str(e))
return False
except accounts.NoAvailableIDs, e:
msgbox(wnd, str(e))
return False
except accounts.InvalidArgument, e:
msgbox(wnd, str(e))
return False
except accounts.LDAPException, e:
msgbox(wnd, "Error creating LDAP entry - Contact the Systems Administrator: %s" % e)
return False
except accounts.KrbException, e:
msgbox(wnd, "Error creating Kerberos principal - Contact the Systems Administrator: %s" % e)
return False
# success
msgbox(wnd, "Please run 'addhomedir " + member['uid'][0] + "'.")
msgbox(wnd, "Success! Your account has been added")
return False
def display_member_details(wnd, member):
"""Display member attributes in a message box."""
# clone and sort term_list
if 'term' in member:
term_list = list(member['term'])
else:
term_list = []
term_list.sort( terms.compare )
# labels for data
id_label, name_label = "ID:", "Name:"
program_label, terms_label = "Program:", "Terms:"
if 'program' in member:
program = member['program'][0]
else:
program = None
# format it all into a massive string
message = "%8s %-20s %10s %-10s\n" % (name_label, member['cn'][0], id_label, member['uid'][0]) + \
"%8s %-20s\n" % (program_label, program )
message += "%s %s" % (terms_label, " ".join(term_list))
# display the string in a message box
msgbox(wnd, message)
def action_display_member(wnd):
"""Interactively display a member."""
if not members.connected(): members.connect()
member = read_member(wnd)
return False
def page(text):
"""Send a text buffer to an external pager for display."""
try:
pager = '/usr/bin/less'
pipe = os.popen(pager, 'w')
pipe.write(text)
pipe.close()
except IOError:
# broken pipe (user didn't read the whole list)
pass
def format_members(member_list):
"""Format a member list into a string."""
# clone and sort member_list
member_list = list(member_list)
member_list.sort( lambda x, y: cmp(x['uid'], y['uid']) )
buf = ''
for member in member_list:
if 'uid' in member:
uid = member['uid'][0]
else:
uid = None
if 'program' in member:
program = member['program'][0]
else:
program = None
attrs = ( uid, member['cn'][0], program )
buf += "%10s %30s\n%41s\n\n" % attrs
return buf
def action_list_term(wnd):
"""Interactively list members registered in a term."""
term = None
# read the term
prompt = "Which term to list members for ([fws]20nn): "
while term is None or (not term == '' and not re.match('^[wsf][0-9]{4}$', term) and not term == 'exit'):
term = inputbox(wnd, prompt, 41)
# abort when exit is entered
if not term or term.lower() == 'exit':
return False
# connect the members module to its backends if necessary
if not members.connected(): members.connect()
# retrieve a list of members for term
member_list = members.list_term(term)
# format the data into a mess of text
buf = format_members(member_list.values())
# display the mass of text with a pager
page( buf )
return False
def action_list_name(wnd):
"""Interactively search for members by name."""
name = None
# read the name
prompt = "Enter the member's name: "
name = inputbox(wnd, prompt, 41)
# abort when exit is entered
if not name or name.lower() == 'exit':
return False
# connect the members module to its backends if necessary
if not members.connected(): members.connect()
# retrieve a list of members with similar names
member_list = members.list_name(name)
# format the data into a mess of text
buf = format_members(member_list.values())
# display the mass of text with a pager
page( buf )
return False
def null_callback(wnd):
"""Callback for unimplemented menu options."""
return False
def exit_callback(wnd):
"""Callback for the exit option."""
return True
# the top level ceo menu
top_menu = [
( "New member", action_new_member ),
( "Register for a term", action_term_register ),
( "Register for multiple terms", action_term_register_multiple ),
( "Display a member", action_display_member ),
( "List members registered in a term", action_list_term ),
( "Search for a member by name", action_list_name ),
( "Create an account", action_create_account ),
( "Library functions", action_library ),
( "Exit", exit_callback ),
]
def acquire_ceo_wnd(screen=None):
"""Create the top level ceo window."""
# hack to get a reference to the entire screen
# even when the caller doesn't (shouldn't) have one
if screen is None:
screen = globals()['screen']
else:
globals()['screen'] = screen
# if the screen changes size, a mess may be left
screen.erase()
# for some reason, the legacy ceo system
# excluded the top line from its window
height, width = screen.getmaxyx()
ceo_wnd = screen.subwin(height-1, width, 1, 0)
# draw the border around the ceo window
curses.init_pair(1, BORDER_COLOR, -1)
color_attr = curses.color_pair(1) | curses.A_BOLD
ceo_wnd.attron(color_attr)
ceo_wnd.border()
ceo_wnd.attroff(color_attr)
# return window and dimensions of inner area
return ceo_wnd, 1, 1, height-2, width-2
def ceo_main_curses(screen):
"""Wrapped main for curses."""
curses.use_default_colors()
# workaround for SSH sessions on virtual consoles (reset terminal)
reset()
# create ceo window
ceo_wnd, menu_y, menu_x, menu_height, menu_width = acquire_ceo_wnd(screen)
try:
# display the top level menu
menu(ceo_wnd, menu_y, menu_x, menu_width, top_menu, acquire_ceo_wnd)
finally:
members.disconnect()
accounts.disconnect()
def run():
"""Main function for legacy UI."""
# workaround for xterm-color (bad terminfo? - curs_set(0) fails)
if "TERM" in os.environ and os.environ['TERM'] == "xterm-color":
os.environ['TERM'] = "xterm"
# wrap the entire program using curses.wrapper
# so that the terminal is restored to a sane state
# when the program exits
try:
curses.wrapper(ceo_main_curses)
except KeyboardInterrupt:
pass
except curses.error:
print "Is your screen too small?"
raise
except:
reset()
raise
# clean up screen before exit
reset()
if __name__ == '__main__':
run()