Mercurial > p > roundup > code
diff roundup/cgi/client.py @ 1003:f89b8d32291b
Hack hack hack...
. Implemented security assertion idea punted to mailing list (pretty easy to
back out if someone comes up with a better idea) so editing "my details"
works again. Rationalised and cleaned up the actions in any case.
. fixed some more display issues (stuff appearing when it should and shouldn't)
. trying a nicer colouring scheme for the top level page
. handle no grouping being specified
. fixed journaltag so the logged-in user is journalled, not admin!
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sun, 01 Sep 2002 12:18:41 +0000 |
| parents | 1798d2fa9fec |
| children | 5f12d3259f31 |
line wrap: on
line diff
--- a/roundup/cgi/client.py Sun Sep 01 04:32:30 2002 +0000 +++ b/roundup/cgi/client.py Sun Sep 01 12:18:41 2002 +0000 @@ -1,4 +1,4 @@ -# $Id: client.py,v 1.2 2002-09-01 04:32:30 richard Exp $ +# $Id: client.py,v 1.3 2002-09-01 12:18:40 richard Exp $ __doc__ = """ WWW request handler (also used in the stand-alone server). @@ -171,6 +171,9 @@ else: self.user = user + # reopen the database as the correct user + self.opendb(self.user) + def determine_context(self, dre=re.compile(r'([^\d]+)(\d+)')): ''' Determine the context of this page: @@ -291,12 +294,12 @@ # these are the actions that are available actions = { - 'edit': 'edititem_action', - 'new': 'newitem_action', + 'edit': 'editItemAction', + 'new': 'newItemAction', 'login': 'login_action', 'logout': 'logout_action', 'register': 'register_action', - 'search': 'search_action', + 'search': 'searchAction', } def handle_action(self): ''' Determine whether there should be an _action called. @@ -304,12 +307,12 @@ The action is defined by the form variable :action which identifies the method on this object to call. The four basic actions are defined in the "actions" dictionary on this class: - "edit" -> self.edititem_action - "new" -> self.newitem_action + "edit" -> self.editItemAction + "new" -> self.newItemAction "login" -> self.login_action "logout" -> self.logout_action "register" -> self.register_action - "search" -> self.search_action + "search" -> self.searchAction ''' if not self.form.has_key(':action'): @@ -454,12 +457,10 @@ path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'], '')) self.header(headers={'Set-Cookie': - 'roundup_user=deleted; Max-Age=0; expires=%s; Path=%s;'%(now, path)}) -# 'Location': self.db.config.DEFAULT_VIEW}, response=301) + 'roundup_user=deleted; Max-Age=0; expires=%s; Path=%s;'%(now, path)}) - # suboptimal, but will do for now + # Let the user know what's going on self.ok_message.append(_('You are logged out')) - #raise Redirect, None def register_action(self): '''Attempt to create a new user based on the contents of the form @@ -497,7 +498,7 @@ # nice message self.ok_message.append(_('You are now registered, welcome!')) - def edititem_action(self): + def editItemAction(self): ''' Perform an edit of an item in the database. Some special form elements: @@ -516,24 +517,26 @@ ''' cl = self.db.classes[self.classname] + # parse the props from the form + try: + props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) + except (ValueError, KeyError), message: + self.error_message.append(_('Error: ') + str(message)) + return + # check permission - userid = self.db.user.lookup(self.user) - if not self.db.security.hasPermission('Edit', userid, self.classname): + if not self.editItemPermission(props): self.error_message.append( - _('You do not have permission to edit %(classname)s' % + _('You do not have permission to edit %(classname)s'% self.__dict__)) return # perform the edit try: - 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) - except (ValueError, KeyError), message: self.error_message.append(_('Error: ') + str(message)) return @@ -556,14 +559,46 @@ raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, self.nodeid, urllib.quote(message)) - def newitem_action(self): + def editItemPermission(self, props): + ''' Determine whether the user has permission to edit this item. + + Base behaviour is to check the user can edit this class. If we're + editing the "user" class, users are allowed to edit their own + details. Unless it's the "roles" property, which requires the + special Permission "Web Roles". + ''' + # if this is a user node and the user is editing their own node, then + # we're OK + has = self.db.security.hasPermission + if self.classname == 'user': + # reject if someone's trying to edit "roles" and doesn't have the + # right permission. + if props.has_key('roles') and not has('Web Roles', self.userid, + 'user'): + return 0 + # if the item being edited is the current user, we're ok + if self.nodeid == self.userid: + return 1 + if not self.db.security.hasPermission('Edit', self.userid, + self.classname): + return 0 + return 1 + + def newItemAction(self): ''' Add a new item to the database. - This follows the same form as the edititem_action + This follows the same form as the editItemAction ''' - # check permission - userid = self.db.user.lookup(self.user) - if not self.db.security.hasPermission('Edit', userid, self.classname): + cl = self.db.classes[self.classname] + + # parse the props from the form + try: + props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) + except (ValueError, KeyError), message: + self.error_message.append(_('Error: ') + str(message)) + return + + if not self.newItemPermission(props): self.error_message.append( _('You do not have permission to create %s' %self.classname)) @@ -578,7 +613,7 @@ try: # do the create - nid = self._createnode() + nid = self._createnode(props) # handle linked nodes self._post_editnode(nid) @@ -606,20 +641,33 @@ raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, nid, urllib.quote(message)) - def genericedit_action(self): + def newItemPermission(self, props): + ''' Determine whether the user has permission to create (edit) this + item. + + Base behaviour is to check the user can edit this class. No + additional property checks are made. Additionally, new user items + may be created if the user has the "Web Registration" Permission. + ''' + has = self.db.security.hasPermission + if self.classname == 'user' and has('Web Registration', self.userid, + 'user'): + return 1 + if not has('Edit', self.userid, self.classname): + return 0 + return 1 + + def genericEditAction(self): ''' Performs an edit of all of a class' items in one go. The "rows" CGI var defines the CSV-formatted entries for the class. New nodes are identified by the ID 'X' (or any other non-existent ID) and removed lines are retired. ''' - userid = self.db.user.lookup(self.user) - if not self.db.security.hasPermission('Edit', userid, self.classname): - raise Unauthorised, _("You do not have permission to access"\ - " %(action)s.")%{'action': self.classname} - cl = self.db.classes[self.classname] - idlessprops = cl.getprops(protected=0).keys() - props = ['id'] + idlessprops + # generic edit is per-class only + if not self.genericEditPermission(): + self.error_message.append( + _('You do not have permission to edit %s' %self.classname)) # get the CSV module try: @@ -630,6 +678,10 @@ 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/')) return + cl = self.db.classes[self.classname] + idlessprops = cl.getprops(protected=0).keys() + props = ['id'] + idlessprops + # do the edit rows = self.form['rows'].value.splitlines() p = csv.parser() @@ -680,6 +732,77 @@ raise Redirect, '%s/%s?:ok_message=%s'%(self.base, self.classname, urllib.quote(message)) + def genericEditPermission(self): + ''' Determine whether the user has permission to edit this class. + + Base behaviour is to check the user can edit this class. + ''' + if not self.db.security.hasPermission('Edit', self.userid, + self.classname): + return 0 + return 1 + + def searchAction(self): + ''' Mangle some of the form variables. + + Set the form ":filter" variable based on the values of the + filter variables - if they're set to anything other than + "dontcare" then add them to :filter. + ''' + # generic edit is per-class only + if not self.searchPermission(): + self.error_message.append( + _('You do not have permission to search %s' %self.classname)) + + # add a faked :filter form variable for each filtering prop + props = self.db.classes[self.classname].getprops() + for key in self.form.keys(): + if not props.has_key(key): continue + if not self.form[key].value: continue + self.form.value.append(cgi.MiniFieldStorage(':filter', key)) + + def searchPermission(self): + ''' Determine whether the user has permission to search this class. + + Base behaviour is to check the user can view this class. + ''' + if not self.db.security.hasPermission('View', self.userid, + self.classname): + return 0 + return 1 + + def XXXremove_action(self, dre=re.compile(r'([^\d]+)(\d+)')): + # XXX I believe this could be handled by a regular edit action that + # just sets the multilink... + # XXX handle this ! + 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 + + # + # Utility methods for editing + # def _changenode(self, props): ''' change the node based on the contents of the form ''' @@ -695,11 +818,10 @@ # make the changes return cl.set(self.nodeid, **props) - def _createnode(self): + def _createnode(self, props): ''' create a node based on the contents of the form ''' cl = self.db.classes[self.classname] - props = parsePropsFromForm(self.db, cl, self.form) # check for messages and files message, files = self._handle_message() @@ -806,47 +928,6 @@ link = self.db.classes[link] link.set(nodeid, **{property: nid}) - def search_action(self): - ''' Mangle some of the form variables. - - Set the form ":filter" variable based on the values of the - filter variables - if they're set to anything other than - "dontcare" then add them to :filter. - ''' - # add a faked :filter form variable for each filtering prop - props = self.db.classes[self.classname].getprops() - for key in self.form.keys(): - if not props.has_key(key): continue - if not self.form[key].value: continue - self.form.value.append(cgi.MiniFieldStorage(':filter', key)) - - def remove_action(self, dre=re.compile(r'([^\d]+)(\d+)')): - # XXX handle this ! - 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 - def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')): '''Pull properties for the given class out of the form.
