diff --git a/roundup/cgi_client.py b/roundup/cgi_client.py
index 715d02d61bd035b9fb0eecd1305fcf3aaa065041..1f1c02dd35b21eb939bb0ee7e2f8267a61dcfa9d 100644 (file)
--- a/roundup/cgi_client.py
+++ b/roundup/cgi_client.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: cgi_client.py,v 1.142 2002-07-18 11:17:30 gmcm Exp $
+# $Id: cgi_client.py,v 1.162 2002-08-23 04:42:30 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
class NotFound(ValueError):
pass
+def initialiseSecurity(security):
+ ''' Create some Permissions and Roles on the security object
+
+ This function is directly invoked by security.Security.__init__()
+ as a part of the Security object instantiation.
+ '''
+ security.addPermission(name="Web Registration",
+ description="User may register through the web")
+ p = security.addPermission(name="Web Access",
+ description="User may access the web interface")
+ security.addPermissionToRole('Admin', p)
+
+ # doing Role stuff through the web - make sure Admin can
+ p = security.addPermission(name="Web Roles",
+ description="User may manipulate user Roles through the web")
+ security.addPermissionToRole('Admin', p)
+
class Client:
'''
A note about login
err = _("sanity check: unknown user name `%s'")%self.user
raise Unauthorised, errmsg
- def header(self, headers=None):
+ def header(self, headers=None, response=200):
'''Put up the appropriate header.
'''
if headers is None:
headers = {'Content-Type':'text/html'}
if not headers.has_key('Content-Type'):
headers['Content-Type'] = 'text/html'
- self.request.send_response(200)
+ self.request.send_response(response)
for entry in headers.items():
self.request.send_header(*entry)
self.request.end_headers()
style = open(os.path.join(self.instance.TEMPLATES, 'style.css')).read()
# figure who the user is
- user_name = self.user or ''
- if user_name not in ('', 'anonymous'):
- userid = self.db.user.lookup(self.user)
- else:
- userid = None
+ user_name = self.user
+ userid = self.db.user.lookup(user_name)
+ default_queries = 1
+ links = []
+ if user_name != 'anonymous':
+ try:
+ default_queries = self.db.user.get(userid, 'defaultqueries')
+ except KeyError:
+ pass
# figure all the header links
- if hasattr(self.instance, 'HEADER_INDEX_LINKS'):
- links = []
- for name in self.instance.HEADER_INDEX_LINKS:
- spec = getattr(self.instance, name + '_INDEX')
- # skip if we need to fill in the logged-in user id there's
- # no user logged in
- if (spec['FILTERSPEC'].has_key('assignedto') and
- spec['FILTERSPEC']['assignedto'] in ('CURRENT USER',
- None) and userid is None):
- continue
- links.append(self.make_index_link(name))
- else:
- # no config spec - hard-code
- links = [
- _('All <a href="issue?status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=-activity&:filter=status&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>'),
- _('Unassigned <a href="issue?assignedto=-1&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=-activity&:filter=status,assignedto&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>')
- ]
-
- if userid:
+ if default_queries:
+ if hasattr(self.instance, 'HEADER_INDEX_LINKS'):
+ for name in self.instance.HEADER_INDEX_LINKS:
+ spec = getattr(self.instance, name + '_INDEX')
+ # skip if we need to fill in the logged-in user id and
+ # we're anonymous
+ if (spec['FILTERSPEC'].has_key('assignedto') and
+ spec['FILTERSPEC']['assignedto'] in ('CURRENT USER',
+ None) and user_name == 'anonymous'):
+ continue
+ links.append(self.make_index_link(name))
+ else:
+ # no config spec - hard-code
+ links = [
+ _('All <a href="issue?status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=-activity&:filter=status&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>'),
+ _('Unassigned <a href="issue?assignedto=-1&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=-activity&:filter=status,assignedto&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>')
+ ]
+
+ user_info = _('<a href="login">Login</a>')
+ add_links = ''
+ if user_name != 'anonymous':
# add any personal queries to the menu
try:
queries = self.db.getclass('query')
except KeyError:
# no query class
- queries = self.instance.dbinit.Class(self.db,
- "query",
- klass=hyperdb.String(),
- name=hyperdb.String(),
- url=hyperdb.String())
+ queries = self.instance.dbinit.Class(self.db, "query",
+ klass=hyperdb.String(), name=hyperdb.String(),
+ url=hyperdb.String())
queries.setkey('name')
-#queries.disableJournalling()
+ #queries.disableJournalling()
try:
qids = self.db.getclass('user').get(userid, 'queries')
except KeyError, e:
#self.db.getclass('user').addprop(queries=hyperdb.Multilink('query'))
qids = []
for qid in qids:
- links.append('<a href=%s?%s>%s</a>' % (queries.get(qid, 'klass'),
- queries.get(qid, 'url'),
- queries.get(qid, 'name')))
-
- # if they're logged in, include links to their information, and the
- # ability to add an issue
- if user_name not in ('', 'anonymous'):
+ links.append('<a href=%s?%s>%s</a>'%(queries.get(qid, 'klass'),
+ queries.get(qid, 'url'), queries.get(qid, 'name')))
+
+ # if they're logged in, include links to their information,
+ # and the ability to add an issue
user_info = _('''
<a href="user%(userid)s">My Details</a> | <a href="logout">Logout</a>
''')%locals()
- # figure the "add class" links
- if hasattr(self.instance, 'HEADER_ADD_LINKS'):
- classes = self.instance.HEADER_ADD_LINKS
- else:
- classes = ['issue']
- l = []
- for class_name in classes:
- cap_class = class_name.capitalize()
- links.append(_('Add <a href="new%(class_name)s">'
- '%(cap_class)s</a>')%locals())
-
- # if there's no config header link spec, force a user link here
- if not hasattr(self.instance, 'HEADER_INDEX_LINKS'):
- links.append(_('<a href="issue?assignedto=%(userid)s&status=-1,unread,chatting,open,pending&:filter=status,resolution,assignedto&:sort=-activity&:columns=id,activity,status,resolution,title,creator&:group=type&show_customization=1">My Issues</a>')%locals())
+ # figure the "add class" links
+ if hasattr(self.instance, 'HEADER_ADD_LINKS'):
+ classes = self.instance.HEADER_ADD_LINKS
else:
- user_info = _('<a href="login">Login</a>')
- add_links = ''
+ classes = ['issue']
+ l = []
+ for class_name in classes:
+ # make sure the user has permission to add
+ if not self.db.security.hasPermission('Edit', userid, class_name):
+ continue
+ cap_class = class_name.capitalize()
+ links.append(_('Add <a href="new%(class_name)s">'
+ '%(cap_class)s</a>')%locals())
- # if the user is admin, include admin links
+ # if the user can edit everything, include the links
admin_links = ''
- if user_name == 'admin':
+ userid = self.db.user.lookup(user_name)
+ if self.db.security.hasPermission('Edit', userid):
links.append(_('<a href="list_classes">Class List</a>'))
- links.append(_('<a href="user?:sort=username">User List</a>'))
+ links.append(_('<a href="user?:sort=username&:group=roles">User List</a>'))
links.append(_('<a href="newuser">Add User</a>'))
# add the search links
classes = ['issue']
l = []
for class_name in classes:
+ # make sure the user has permission to view
+ if not self.db.security.hasPermission('View', userid, class_name):
+ continue
cap_class = class_name.capitalize()
links.append(_('Search <a href="search%(class_name)s">'
'%(cap_class)s</a>')%locals())
return []
def index_sort(self):
- # first try query string
+ # first try query string / simple form
x = self.index_arg(':sort')
if x:
+ if self.index_arg(':descending'):
+ return ['-'+x[0]]
return x
# nope - get the specs out of the form
specs = []
all_columns = self.db.getclass(cn).getprops().keys()
all_columns.sort()
index.filter_section('', filter, columns, group, all_columns, sort,
- filterspec, pagesize, 0)
+ filterspec, pagesize, 0, 0)
self.pagefoot()
- index.db = index.cl = index.properties = None
- index.clear()
# XXX deviates from spec - loses the '+' (that's a reserved character
# in URLS
def list(self, sort=None, group=None, filter=None, columns=None,
- filterspec=None, show_customization=None, show_nodes=1, pagesize=None):
+ filterspec=None, show_customization=None, show_nodes=1,
+ pagesize=None):
''' call the template index with the args
:sort - sort by prop name, optionally preceeded with '-'
'''
cn = self.classname
cl = self.db.classes[cn]
- self.pagehead(_('%(instancename)s: Index of %(classname)s')%{
- 'classname': cn, 'instancename': self.instance.INSTANCE_NAME})
if sort is None: sort = self.index_sort()
if group is None: group = self.index_arg(':group')
if filter is None: filter = self.index_arg(':filter')
startwith = int(self.form[':startwith'].value)
else:
startwith = 0
+ simpleform = 1
+ if self.form.has_key(':advancedsearch'):
+ simpleform = 0
if self.form.has_key('Query') and self.form['Query'].value == 'Save':
# format a query string
# create a query
d = {}
- d['name'] = self.form[':name'].value
+ d['name'] = nm = self.form[':name'].value
+ if not nm:
+ d['name'] = nm = 'New Query'
d['klass'] = self.form[':classname'].value
d['url'] = url
qid = self.db.getclass('query').create(**d)
queries.append(qid)
usercl.set(uid, queries=queries)
+ self.pagehead(_('%(instancename)s: Index of %(classname)s')%{
+ 'classname': cn, 'instancename': self.instance.INSTANCE_NAME})
+
index = htmltemplate.IndexTemplate(self, self.instance.TEMPLATES, cn)
try:
- index.render(filterspec, search_text, filter, columns, sort,
- group, show_customization=show_customization,
- show_nodes=show_nodes, pagesize=pagesize, startwith=startwith)
+ index.render(filterspec=filterspec, search_text=search_text,
+ filter=filter, columns=columns, sort=sort, group=group,
+ show_customization=show_customization,
+ show_nodes=show_nodes, pagesize=pagesize, startwith=startwith,
+ simple_search=simpleform)
except htmltemplate.MissingTemplateError:
self.basicClassEditPage()
self.pagefoot()
'''Display a basic edit page that allows simple editing of the
nodes of the current class
'''
- if self.user != 'admin':
- raise Unauthorised
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': self.classname}
w = self.write
cn = self.classname
cl = self.db.classes[cn]
idlessprops = cl.getprops(protected=0).keys()
props = ['id'] + idlessprops
-
# get the CSV module
try:
import csv
# extract the new values
d = {}
for name, value in zip(idlessprops, values):
- d[name] = value.strip()
+ value = value.strip()
+ # only add the property if it has a value
+ if value:
+ # if it's a multilink, split it
+ if isinstance(cl.properties[name], hyperdb.Multilink):
+ value = value.split(':')
+ d[name] = value
# perform the edit
if cl.hasnode(nodeid):
cl.retire(nodeid)
w(_('''<p class="form-help">You may edit the contents of the
- "%(classname)s" class using this form. The lines are full-featured
- Comma-Separated-Value lines, so you may include commas and even
+ "%(classname)s" class using this form. Commas, newlines and double
+ quotes (") must be handled delicately. You may include commas and
newlines by enclosing the values in double-quotes ("). Double
quotes themselves must be quoted by doubling ("").</p>
+ <p class="form-help">Multilink properties have their multiple
+ values colon (":") separated (... ,"one:two:three", ...)</p>
<p class="form-help">Remove entries by deleting their line. Add
new entries by appending
them to the table - put an X in the id column.</p>''')%{'classname':cn})
for nodeid in cl.list():
l = []
for name in props:
- l.append(cgi.escape(str(cl.get(nodeid, name))))
+ value = cl.get(nodeid, name)
+ if value is None:
+ l.append('')
+ elif isinstance(value, type([])):
+ l.append(cgi.escape(':'.join(map(str, value))))
+ else:
+ l.append(cgi.escape(str(cl.get(nodeid, name))))
w(p.join(l) + '\n')
w(_('</textarea><br><input type="submit" value="Save Changes"></form>'))
for name in props:
w('<th align=left>%s</th>'%name)
w('</tr>')
- for nodeid in cl.filter(None, {}, sort, []): #cl.list():
+ for nodeid in cl.filter(None, {}, sort, []):
w('<tr>')
for name in props:
value = cgi.escape(str(cl.get(nodeid, name)))
'''
cn = self.classname
cl = self.db.classes[cn]
+ keys = self.form.keys()
+ fromremove = 0
if self.form.has_key(':multilink'):
- link = self.form[':multilink'].value
- designator, linkprop = link.split(':')
- xtra = ' for <a href="%s">%s</a>' % (designator, designator)
+ # is the multilink there because we came from remove()?
+ if self.form.has_key(':target'):
+ xtra = ''
+ fromremove = 1
+ message = _('%s removed' % self.index_arg(":target")[0])
+ else:
+ link = self.form[':multilink'].value
+ designator, linkprop = link.split(':')
+ xtra = ' for <a href="%s">%s</a>' % (designator, designator)
else:
xtra = ''
-
+
# possibly perform an edit
- keys = self.form.keys()
# don't try to set properties if the user has just logged in
- if keys and not self.form.has_key('__login_name'):
+ if keys and not fromremove and not self.form.has_key('__login_name'):
try:
- props = parsePropsFromForm(self.db, cl, self.form, self.nodeid)
- # make changes to the node
- self._changenode(props)
- # handle linked nodes
- self._post_editnode(self.nodeid)
- # and some nice feedback for the user
- if props:
- message = _('%(changes)s edited ok')%{'changes':
- ', '.join(props.keys())}
- elif self.form.has_key('__note') and self.form['__note'].value:
- message = _('note added')
- elif (self.form.has_key('__file') and
- self.form['__file'].filename):
- message = _('file added')
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid, cn):
+ message = _('You do not have permission to edit %s' %cn)
else:
- message = _('nothing changed')
+ props = parsePropsFromForm(self.db, cl, self.form, self.nodeid)
+ # make changes to the node
+ props = self._changenode(props)
+ # handle linked nodes
+ self._post_editnode(self.nodeid)
+ # and some nice feedback for the user
+ if props:
+ message = _('%(changes)s edited ok')%{'changes':
+ ', '.join(props.keys())}
+ elif self.form.has_key('__note') and self.form['__note'].value:
+ message = _('note added')
+ elif (self.form.has_key('__file') and
+ self.form['__file'].filename):
+ message = _('file added')
+ else:
+ message = _('nothing changed')
except:
self.db.rollback()
s = StringIO.StringIO()
filterspec = self.index_filterspec(filter, queries.get(self.nodeid, 'klass'))
if self.form.has_key('search_text'):
search_text = self.form['search_text'].value
+ search_text = urllib.quote(search_text)
else:
search_text = ''
if self.form.has_key(':pagesize'):
url += '&:pagesize=%s' % pagesize
if search_text:
url += '&search_text=%s' % search_text
- qname = self.form['name'].value
- chgd = []
- if qname != queries.get(self.nodeid, 'name'):
- chgd.append('name')
if url != queries.get(self.nodeid, 'url'):
- chgd.append('url')
- if chgd:
- queries.set(self.nodeid, name=qname, url=url)
- message = _('%(changes)s edited ok')%{'changes': ', '.join(chgd)}
+ queries.set(self.nodeid, url=url)
+ message = _('url edited ok')
else:
message = _('nothing changed')
else:
props['files'] = cl.get(self.nodeid, 'files') + files
# make the changes
- cl.set(self.nodeid, **props)
+ return cl.set(self.nodeid, **props)
def _createnode(self):
''' create a node based on the contents of the form
if type(value) != type([]): value = [value]
for value in value:
designator, property = value.split(':')
- link, nodeid = roundupdb.splitDesignator(designator)
+ link, nodeid = hyperdb.splitDesignator(designator)
link = self.db.classes[link]
# take a dupe of the list so we're not changing the cache
value = link.get(nodeid, property)[:]
if type(value) != type([]): value = [value]
for value in value:
designator, property = value.split(':')
- link, nodeid = roundupdb.splitDesignator(designator)
+ link, nodeid = hyperdb.splitDesignator(designator)
link = self.db.classes[link]
link.set(nodeid, **{property: nid})
node's id. The node id will be appended to the multilink.
'''
cn = self.classname
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('View', userid, cn):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': self.classname}
cl = self.db.classes[cn]
if self.form.has_key(':multilink'):
link = self.form[':multilink'].value
# possibly perform a create
keys = self.form.keys()
if [i for i in keys if i[0] != ':']:
+ # no dice if you can't edit!
+ if not self.db.security.hasPermission('Edit', userid, cn):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': 'new'+self.classname}
props = {}
try:
nid = self._createnode()
Don't do any of the message or file handling, just create the node.
'''
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid, 'user'):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': 'newuser'}
+
cn = self.classname
cl = self.db.classes[cn]
This form works very much the same way as newnode - it just has a
file upload.
'''
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid, 'file'):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': 'newfile'}
cn = self.classname
cl = self.db.classes[cn]
props = parsePropsFromForm(self.db, cl, self.form)
def showuser(self, message=None, num_re=re.compile('^\d+$')):
'''Display a user page for editing. Make sure the user is allowed
- to edit this node, and also check for password changes.
- '''
- if self.user == 'anonymous':
- raise Unauthorised
+ to edit this node, and also check for password changes.
+ Note: permission checks for this node are handled in the template.
+ '''
user = self.db.user
# get the username of the node being edited
- node_user = user.get(self.nodeid, 'username')
+ try:
+ node_user = user.get(self.nodeid, 'username')
+ except IndexError:
+ raise NotFound, 'user%s'%self.nodeid
- if self.user not in ('admin', node_user):
- raise Unauthorised
-
#
# perform any editing
#
def showfile(self):
''' display a file
'''
+ # nothing in xtrapath - edit the file's metadata
+ if self.xtrapath is None:
+ return self.shownode()
+
+ # something in xtrapath - download the file
nodeid = self.nodeid
cl = self.db.classes[self.classname]
- mime_type = cl.get(nodeid, 'type')
+ try:
+ mime_type = cl.get(nodeid, 'type')
+ except IndexError:
+ raise NotFound, 'file%s'%nodeid
if mime_type == 'message/rfc822':
mime_type = 'text/plain'
self.header(headers={'Content-Type': mime_type})
self.write(cl.get(nodeid, 'content'))
+
+ def permission(self):
+ '''
+ '''
def classes(self, message=None):
''' display a list of all the classes in the database
'''
- if self.user == 'admin':
- self.pagehead(_('Table of classes'), message)
- classnames = self.db.classes.keys()
- classnames.sort()
- self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
- for cn in classnames:
- cl = self.db.getclass(cn)
- self.write('<tr class="list-header"><th colspan=2 align=left>'
- '<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
- for key, value in cl.properties.items():
- if value is None: value = ''
- else: value = str(value)
- self.write('<tr><th align=left>%s</th><td>%s</td></tr>'%(
- key, cgi.escape(value)))
- self.write('</table>')
- self.pagefoot()
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Edit', userid):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': 'all classes'}
+
+ self.pagehead(_('Table of classes'), message)
+ classnames = self.db.classes.keys()
+ classnames.sort()
+ self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
+ for cn in classnames:
+ cl = self.db.getclass(cn)
+ self.write('<tr class="list-header"><th colspan=2 align=left>'
+ '<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
+ for key, value in cl.properties.items():
+ if value is None: value = ''
+ else: value = str(value)
+ self.write('<tr><th align=left>%s</th><td>%s</td></tr>'%(
+ key, cgi.escape(value)))
+ self.write('</table>')
+ self.pagefoot()
+
+ def unauthorised(self, message):
+ ''' The user is not authorised to do something. If they're
+ anonymous, throw up a login box. If not, just tell them they
+ can't do whatever it was they were trying to do.
+
+ Bot cases print up the message, which is most likely the
+ argument to the Unauthorised exception.
+ '''
+ self.header(response=403)
+ if self.desired_action is None or self.desired_action == 'login':
+ if not message:
+ message=_("You do not have permission.")
+ action = 'index'
else:
- raise Unauthorised
+ if not message:
+ message=_("You do not have permission to access"\
+ " %(action)s.")%{'action': self.desired_action}
+ action = self.desired_action
+ if self.user == 'anonymous':
+ self.login(action=action, message=message)
+ else:
+ self.pagehead(_('Not Authorised'))
+ self.write('<p class="system-msg">%s</p>'%message)
+ self.pagefoot()
def login(self, message=None, newuser_form=None, action='index'):
'''Display a login page.
'''
- self.pagehead(_('Login to roundup'), message)
+ self.pagehead(_('Login to roundup'))
+ if message:
+ self.write('<p class="system-msg">%s</p>'%message)
self.write(_('''
<table>
<tr><td colspan=2 class="strong-header">Existing User Login</td></tr>
<td><input type="submit" value="Log In"></td></tr>
</form>
''')%locals())
- if self.user is None and self.instance.ANONYMOUS_REGISTER == 'deny':
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Web Registration', userid):
self.write('</table>')
self.pagefoot()
return
return 1 on successful login
'''
- # re-open the database as "admin"
- self.opendb('admin')
+ # make sure we're allowed to register
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Web Registration', userid):
+ raise Unauthorised, _("You do not have permission to access"\
+ " %(action)s.")%{'action': 'registration'}
+ # re-open the database as "admin"
+ if self.user != 'admin':
+ self.opendb('admin')
+
# create the new user
cl = self.db.user
try:
props = parsePropsFromForm(self.db, cl, self.form)
+ props['roles'] = self.instance.NEW_WEB_USER_ROLES
uid = cl.create(**props)
+ self.db.commit()
except ValueError, message:
action = self.form['__destination_url'].value
self.login(message, action=action)
# re-open the database for real, using the user
self.opendb(self.user)
password = cl.get(uid, 'password')
- self.set_cookie(self.user, self.form['password'].value)
+ self.set_cookie(self.user, password)
return 1
def set_cookie(self, user, password):
# TODO generate a much, much stronger session key ;)
- session = binascii.b2a_base64(repr(time.time())).strip()
+ self.session = binascii.b2a_base64(repr(time.time())).strip()
# clean up the base64
- if session[-1] == '=':
- if session[-2] == '=':
- session = session[:-2]
- else:
- session = session[:-1]
-
- print 'session set to', `session`
+ if self.session[-1] == '=':
+ if self.session[-2] == '=':
+ self.session = self.session[:-2]
+ else:
+ self.session = self.session[:-1]
# insert the session in the sessiondb
- sessions = self.db.getclass('__sessions')
- self.session = sessions.create(sessid=session, user=user,
- last_use=date.Date())
+ self.db.sessions.set(self.session, user=user, last_use=time.time())
# and commit immediately
- self.db.commit()
+ self.db.sessions.commit()
# expire us in a long, long time
expire = Cookie._getdate(86400*365)
path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
''))
self.header({'Set-Cookie': 'roundup_user=%s; expires=%s; Path=%s;'%(
- session, expire, path)})
+ self.session, expire, path)})
def make_user_anonymous(self):
- # make us anonymous if we can
- try:
- self.db.user.lookup('anonymous')
- self.user = 'anonymous'
- except KeyError:
- self.user = None
+ ''' Make us anonymous
+
+ This method used to handle non-existence of the 'anonymous'
+ user, but that user is mandatory now.
+ '''
+ self.db.user.lookup('anonymous')
+ self.user = 'anonymous'
def logout(self, message=None):
+ ''' Make us really anonymous - nuke the cookie too
+ '''
self.make_user_anonymous()
+
# construct the logout cookie
now = Cookie._getdate()
path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
self.login()
def opendb(self, user):
- ''' Open the database - but include the definition of the sessions db.
+ ''' Open the database.
'''
- # open the db
- self.db = self.instance.open(user)
+ # open the db if the user has changed
+ if not hasattr(self, 'db') or user != self.db.journaltag:
+ self.db = self.instance.open(user)
- # make sure we have the session Class
+ def main(self):
+ ''' Wrap the request and handle unauthorised requests
+ '''
+ self.desired_action = None
try:
- sessions = self.db.getclass('__sessions')
- except:
- # add the sessions Class - use a non-journalling Class
- # TODO: not happy with how we're getting the Class here :(
- sessions = self.instance.dbinit.Class(self.db, '__sessions',
- sessid=hyperdb.String(), user=hyperdb.String(),
- last_use=hyperdb.Date())
- sessions.setkey('sessid')
- # make sure session db isn't journalled
- sessions.disableJournalling()
+ self.main_action()
+ except Unauthorised, message:
+ self.unauthorised(message)
- def main(self):
+ def main_action(self):
'''Wrap the database accesses so we can close the database cleanly
'''
# determine the uid to use
self.opendb('admin')
# make sure we have the session Class
- sessions = self.db.getclass('__sessions')
+ sessions = self.db.sessions
# age sessions, remove when they haven't been used for a week
# TODO: this shouldn't be done every access
- week = date.Interval('7d')
- now = date.Date()
+ week = 60*60*24*7
+ now = time.time()
for sessid in sessions.list():
interval = now - sessions.get(sessid, 'last_use')
if interval > week:
# look up the user session cookie
cookie = Cookie.Cookie(self.env.get('HTTP_COOKIE', ''))
user = 'anonymous'
+
if (cookie.has_key('roundup_user') and
cookie['roundup_user'].value != 'deleted'):
# get the session key from the cookie
- session = cookie['roundup_user'].value
-
+ self.session = cookie['roundup_user'].value
# get the user from the session
try:
- self.session = sessions.lookup(session)
+ # update the lifetime datestamp
+ sessions.set(self.session, last_use=time.time())
+ sessions.commit()
+ user = sessions.get(self.session, 'user')
except KeyError:
user = 'anonymous'
- else:
- # update the lifetime datestamp
- sessions.set(self.session, last_use=date.Date())
- self.db.commit()
- user = sessions.get(sessid, 'user')
+
+ # sanity check on the user still being valid
+ try:
+ self.db.user.lookup(user)
+ except (KeyError, TypeError):
+ user = 'anonymous'
# make sure the anonymous user is valid if we're using it
if user == 'anonymous':
# now figure which function to call
path = self.split_path
+ self.xtrapath = None
# default action to index if the path has no information in it
if not path or path[0] in ('', 'index'):
action = 'index'
else:
action = path[0]
-
- # Everthing ignores path[1:]
- # - The file download link generator actually relies on this - it
- # appends the name of the file to the URL so the download file name
- # is correct, but doesn't actually use it.
+ if len(path) > 1:
+ self.xtrapath = path[1:]
+ self.desired_action = action
# everyone is allowed to try to log in
if action == 'login_action':
return
# figure the resulting page
action = self.form['__destination_url'].value
- if not action:
- action = 'index'
- self.do_action(action)
- return
# allow anonymous people to register
- if action == 'newuser_action':
- # if we don't have a login and anonymous people aren't allowed to
- # register, then spit up the login form
- if self.instance.ANONYMOUS_REGISTER == 'deny' and self.user is None:
- if action == 'login':
- self.login() # go to the index after login
- else:
- self.login(action=action)
- return
+ elif action == 'newuser_action':
# try to add the user
if not self.newuser_action():
return
# figure the resulting page
action = self.form['__destination_url'].value
- if not action:
- action = 'index'
- # no login or registration, make sure totally anonymous access is OK
- elif self.instance.ANONYMOUS_ACCESS == 'deny' and self.user is None:
- if action == 'login':
- self.login() # go to the index after login
- else:
- self.login(action=action)
- return
+ # ok, now we have figured out who the user is, make sure the user
+ # has permission to use this interface
+ userid = self.db.user.lookup(self.user)
+ if not self.db.security.hasPermission('Web Access', userid):
+ raise Unauthorised, \
+ _("You do not have permission to access this interface.")
# re-open the database for real, using the user
self.opendb(self.user)
- # just a regular action
- self.do_action(action)
+ # make sure we have a sane action
+ if not action:
+ action = 'index'
- # commit all changes to the database
- self.db.commit()
+ # just a regular action
+ try:
+ self.do_action(action)
+ except Unauthorised, message:
+ # if unauth is raised here, then a page header will have
+ # been displayed
+ self.write('<p class="system-msg">%s</p>'%message)
+ else:
+ # commit all changes to the database
+ self.db.commit()
def do_action(self, action, dre=re.compile(r'([^\d]+)(\d+)'),
nre=re.compile(r'new(\w+)'), sre=re.compile(r'search(\w+)')):
if action == 'logout':
self.logout()
return
+ if action == 'remove':
+ self.remove()
+ return
# see if we're to display an existing node
m = dre.match(action)
raise NotFound, self.classname
self.list()
+ def remove(self, dre=re.compile(r'([^\d]+)(\d+)')):
+ target = self.index_arg(':target')[0]
+ m = dre.match(target)
+ if m:
+ classname = m.group(1)
+ nodeid = m.group(2)
+ cl = self.db.getclass(classname)
+ cl.retire(nodeid)
+ # now take care of the reference
+ parentref = self.index_arg(':multilink')[0]
+ parent, prop = parentref.split(':')
+ m = dre.match(parent)
+ if m:
+ self.classname = m.group(1)
+ self.nodeid = m.group(2)
+ cl = self.db.getclass(self.classname)
+ value = cl.get(self.nodeid, prop)
+ value.remove(nodeid)
+ cl.set(self.nodeid, **{prop:value})
+ func = getattr(self, 'show%s'%self.classname)
+ return func()
+ else:
+ raise NotFound, parent
+ else:
+ raise NotFound, target
class ExtendedClient(Client):
'''Includes pages and page heading information that relate to the
value = form[key].value.strip()
# see if it's the "no selection" choice
if value == '-1':
- # don't set this property
- continue
+ value = None
else:
# handle key values
link = cl.properties[key].classname
#
# $Log: not supported by cvs2svn $
+# Revision 1.161 2002/08/19 00:21:10 richard
+# removed debug prints
+#
+# Revision 1.160 2002/08/19 00:20:34 richard
+# grant web access to admin ;)
+#
+# Revision 1.159 2002/08/16 04:29:41 richard
+# bugfix
+#
+# Revision 1.158 2002/08/15 00:40:10 richard
+# cleanup
+#
+# Revision 1.157 2002/08/13 20:16:09 gmcm
+# Use a real parser for templates.
+# Rewrite htmltemplate to use the parser (hack, hack).
+# Move the "do_XXX" methods to template_funcs.py.
+# Redo the funcion tests (but not Template tests - they're hopeless).
+# Simplified query form in cgi_client.
+# Ability to delete msgs, files, queries.
+# Ability to edit the metadata on files.
+#
+# Revision 1.156 2002/08/01 15:06:06 gmcm
+# Use same regex to split search terms as used to index text.
+# Fix to back_metakit for not changing journaltag on reopen.
+# Fix htmltemplate's do_link so [No <whatever>] strings are href'd.
+# Fix bogus "nosy edited ok" msg - the **d syntax does NOT share d between caller and callee.
+#
+# Revision 1.155 2002/08/01 00:56:22 richard
+# Added the web access and email access permissions, so people can restrict
+# access to users who register through the email interface (for example).
+# Also added "security" command to the roundup-admin interface to display the
+# Role/Permission config for an instance.
+#
+# Revision 1.154 2002/07/31 23:57:36 richard
+# . web forms may now unset Link values (like assignedto)
+#
+# Revision 1.153 2002/07/31 22:40:50 gmcm
+# Fixes to the search form and saving queries.
+# Fixes to sorting in back_metakit.py.
+#
+# Revision 1.152 2002/07/31 22:04:14 richard
+# cleanup
+#
+# Revision 1.151 2002/07/30 21:37:43 richard
+# oops, thanks Duncan Booth for spotting this one
+#
+# Revision 1.150 2002/07/30 20:43:18 gmcm
+# Oops, fix the permission check!
+#
+# Revision 1.149 2002/07/30 20:04:38 gmcm
+# Adapt metakit backend to new security scheme.
+# Put some more permission checks in cgi_client.
+#
+# Revision 1.148 2002/07/30 16:09:11 gmcm
+# Simple optimization.
+#
+# Revision 1.147 2002/07/30 08:22:38 richard
+# Session storage in the hyperdb was horribly, horribly inefficient. We use
+# a simple anydbm wrapper now - which could be overridden by the metakit
+# backend or RDB backend if necessary.
+# Much, much better.
+#
+# Revision 1.146 2002/07/30 05:27:30 richard
+# nicer error messages, and a bugfix
+#
+# Revision 1.145 2002/07/26 08:26:59 richard
+# Very close now. The cgi and mailgw now use the new security API. The two
+# templates have been migrated to that setup. Lots of unit tests. Still some
+# issue in the web form for editing Roles assigned to users.
+#
+# Revision 1.144 2002/07/25 07:14:05 richard
+# Bugger it. Here's the current shape of the new security implementation.
+# Still to do:
+# . call the security funcs from cgi and mailgw
+# . change shipped templates to include correct initialisation and remove
+# the old config vars
+# ... that seems like a lot. The bulk of the work has been done though. Honest :)
+#
+# Revision 1.143 2002/07/20 19:29:10 gmcm
+# Fixes/improvements to the search form & saved queries.
+#
+# Revision 1.142 2002/07/18 11:17:30 gmcm
+# Add Number and Boolean types to hyperdb.
+# Add conversion cases to web, mail & admin interfaces.
+# Add storage/serialization cases to back_anydbm & back_metakit.
+#
# Revision 1.141 2002/07/17 12:39:10 gmcm
# Saving, running & editing queries.
#