diff --git a/roundup/cgi/actions.py b/roundup/cgi/actions.py
index 136fb995eae523d8ddd398465249eab8547dd248..36dfee3707f96a02c0c12c0cd49c70f66c8f718c 100755 (executable)
--- a/roundup/cgi/actions.py
+++ b/roundup/cgi/actions.py
-#$Id: actions.py,v 1.73 2008-08-18 05:04:01 richard Exp $
-
import re, cgi, StringIO, urllib, time, random, csv, codecs
from roundup import hyperdb, token, date, password
+from roundup.actions import Action as BaseAction
from roundup.i18n import _
import roundup.exceptions
from roundup.cgi import exceptions, templating
def handle(self):
"""Retire the context item."""
- # if we want to view the index template now, then unset the nodeid
+ # ensure modification comes via POST
+ if self.client.env['REQUEST_METHOD'] != 'POST':
+ raise roundup.exceptions.Reject(self._('Invalid request'))
+
+ # if we want to view the index template now, then unset the itemid
# context info (a special-case for retire actions on the index page)
- nodeid = self.nodeid
+ itemid = self.nodeid
if self.template == 'index':
self.client.nodeid = None
# make sure we don't try to retire admin or anonymous
if self.classname == 'user' and \
- self.db.user.get(nodeid, 'username') in ('admin', 'anonymous'):
+ self.db.user.get(itemid, 'username') in ('admin', 'anonymous'):
raise ValueError, self._(
'You may not retire the admin or anonymous user')
+ # check permission
+ if not self.hasPermission('Retire', classname=self.classname,
+ itemid=itemid):
+ raise exceptions.Unauthorised, self._(
+ 'You do not have permission to retire %(class)s'
+ ) % {'class': self.classname}
+
# do the retire
- self.db.getclass(self.classname).retire(nodeid)
+ self.db.getclass(self.classname).retire(itemid)
self.db.commit()
self.client.ok_message.append(
self._('%(classname)s %(itemid)s has been retired')%{
- 'classname': self.classname.capitalize(), 'itemid': nodeid})
+ 'classname': self.classname.capitalize(), 'itemid': itemid})
- def hasPermission(self, permission, classname=Action._marker, itemid=None):
- if itemid is None:
- itemid = self.nodeid
- return Action.hasPermission(self, permission, classname, itemid)
class SearchAction(Action):
name = 'search'
if isinstance(prop, hyperdb.String):
v = self.form[key].value
l = token.token_split(v)
- if len(l) > 1 or l[0] != v:
+ if len(l) != 1 or l[0] != v:
self.form.value.remove(self.form[key])
# replace the single value with the split list
for v in l:
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.
-
"""
+ # ensure modification comes via POST
+ if self.client.env['REQUEST_METHOD'] != 'POST':
+ raise roundup.exceptions.Reject(self._('Invalid request'))
+
+ # figure the properties list for the class
cl = self.db.classes[self.classname]
- idlessprops = cl.getprops(protected=0).keys()
- idlessprops.sort()
- props = ['id'] + idlessprops
+ props_without_id = cl.getprops(protected=0).keys()
+
+ # the incoming CSV data will always have the properties in colums
+ # sorted and starting with the "id" column
+ props_without_id.sort()
+ props = ['id'] + props_without_id
# do the edit
rows = StringIO.StringIO(self.form['rows'].value)
if values == props:
continue
- # extract the nodeid
- nodeid, values = values[0], values[1:]
- found[nodeid] = 1
+ # extract the itemid
+ itemid, values = values[0], values[1:]
+ found[itemid] = 1
# see if the node exists
- if nodeid in ('x', 'X') or not cl.hasnode(nodeid):
+ if itemid in ('x', 'X') or not cl.hasnode(itemid):
exists = 0
+
+ # check permission to create this item
+ if not self.hasPermission('Create', classname=self.classname):
+ raise exceptions.Unauthorised, self._(
+ 'You do not have permission to create %(class)s'
+ ) % {'class': self.classname}
+ elif cl.hasnode(itemid) and cl.is_retired(itemid):
+ # If a CSV line just mentions an id and the corresponding
+ # item is retired, then the item is restored.
+ cl.restore(itemid)
+ continue
else:
exists = 1
# confirm correct weight
- if len(idlessprops) != len(values):
+ if len(props_without_id) != len(values):
self.client.error_message.append(
self._('Not enough values on line %(line)s')%{'line':line})
return
# extract the new values
d = {}
- for name, value in zip(idlessprops, values):
+ for name, value in zip(props_without_id, values):
+ # check permission to edit this property on this item
+ if exists and not self.hasPermission('Edit', itemid=itemid,
+ classname=self.classname, property=name):
+ raise exceptions.Unauthorised, self._(
+ 'You do not have permission to edit %(class)s'
+ ) % {'class': self.classname}
+
prop = cl.properties[name]
value = value.strip()
# only add the property if it has a value
# perform the edit
if exists:
# edit existing
- cl.set(nodeid, **d)
+ cl.set(itemid, **d)
else:
# new node
found[cl.create(**d)] = 1
# retire the removed entries
- for nodeid in cl.list():
- if not found.has_key(nodeid):
- cl.retire(nodeid)
+ for itemid in cl.list():
+ if not found.has_key(itemid):
+ # check permission to retire this item
+ if not self.hasPermission('Retire', itemid=itemid,
+ classname=self.classname):
+ raise exceptions.Unauthorised, self._(
+ 'You do not have permission to retire %(class)s'
+ ) % {'class': self.classname}
+ cl.retire(itemid)
# all OK
self.db.commit()
if linkid is None or linkid.startswith('-'):
# linking to a new item
if isinstance(propdef, hyperdb.Multilink):
- props[linkprop] = [newid]
+ props[linkprop] = [nodeid]
else:
- props[linkprop] = newid
+ props[linkprop] = nodeid
else:
# linking to an existing item
if isinstance(propdef, hyperdb.Multilink):
existing.append(nodeid)
props[linkprop] = existing
else:
- props[linkprop] = newid
+ props[linkprop] = nodeid
return '<br>'.join(m)
# The user must have permission to edit each of the properties
# being changed.
for p in props:
- if not self.hasPermission('Edit',
- itemid=itemid,
- classname=classname,
- property=p):
+ if not self.hasPermission('Edit', itemid=itemid,
+ classname=classname, property=p):
return 0
# Since the user has permission to edit all of the properties,
# the edit is OK.
Base behaviour is to check the user can edit this class. No additional
property checks are made.
"""
+
if not classname :
classname = self.client.classname
- return self.hasPermission('Create', classname=classname)
+
+ if not self.hasPermission('Create', classname=classname):
+ return 0
+
+ # Check Create permission for each property, to avoid being able
+ # to set restricted ones on new item creation
+ for key in props:
+ if not self.hasPermission('Create', classname=classname,
+ property=key):
+ return 0
+ return 1
class EditItemAction(EditCommon):
def lastUserActivity(self):
See parsePropsFromForm and _editnodes for special variables.
"""
+ # ensure modification comes via POST
+ if self.client.env['REQUEST_METHOD'] != 'POST':
+ raise roundup.exceptions.Reject(self._('Invalid request'))
+
user_activity = self.lastUserActivity()
if user_activity:
props = self.detectCollision(user_activity, self.lastNodeActivity())
This follows the same form as the EditItemAction, with the same
special form values.
'''
+ # ensure modification comes via POST
+ if self.client.env['REQUEST_METHOD'] != 'POST':
+ raise roundup.exceptions.Reject(self._('Invalid request'))
+
# parse the props from the form
try:
props, links = self.client.parsePropsFromForm(create=1)
class RegisterAction(RegoCommon, EditCommon):
name = 'register'
- permissionType = 'Create'
+ permissionType = 'Register'
def handle(self):
"""Attempt to create a new user based on the contents of the form
Return 1 on successful login.
"""
+ # ensure modification comes via POST
+ if self.client.env['REQUEST_METHOD'] != 'POST':
+ raise roundup.exceptions.Reject(self._('Invalid request'))
+
# parse the props from the form
try:
props, links = self.client.parsePropsFromForm(create=1)
Sets up a session for the user which contains the login credentials.
"""
+ # ensure modification comes via POST
+ if self.client.env['REQUEST_METHOD'] != 'POST':
+ raise roundup.exceptions.Reject(self._('Invalid request'))
+
# we need the username at a minimum
if not self.form.has_key('__login_name'):
self.client.error_message.append(self._('Username required'))
# and search
for itemid in klass.filter(matches, filterspec, sort, group):
- self.client._socket_op(writer.writerow, [str(klass.get(itemid, col)) for col in columns])
+ row = []
+ for name in columns:
+ # check permission to view this property on this item
+ if not self.hasPermission('View', itemid=itemid,
+ classname=request.classname, property=name):
+ raise exceptions.Unauthorised, self._(
+ 'You do not have permission to view %(class)s'
+ ) % {'class': request.classname}
+ row.append(str(klass.get(itemid, name)))
+ self.client._socket_op(writer.writerow, row)
return '\n'
+
+class Bridge(BaseAction):
+ """Make roundup.actions.Action executable via CGI request.
+
+ Using this allows users to write actions executable from multiple frontends.
+ CGI Form content is translated into a dictionary, which then is passed as
+ argument to 'handle()'. XMLRPC requests have to pass this dictionary
+ directly.
+ """
+
+ def __init__(self, *args):
+
+ # As this constructor is callable from multiple frontends, each with
+ # different Action interfaces, we have to look at the arguments to
+ # figure out how to complete construction.
+ if (len(args) == 1 and
+ hasattr(args[0], '__class__') and
+ args[0].__class__.__name__ == 'Client'):
+ self.cgi = True
+ self.execute = self.execute_cgi
+ self.client = args[0]
+ self.form = self.client.form
+ else:
+ self.cgi = False
+
+ def execute_cgi(self):
+ args = {}
+ for key in self.form.keys():
+ args[key] = self.form.getvalue(key)
+ self.permission(args)
+ return self.handle(args)
+
+ def permission(self, args):
+ """Raise Unauthorised if the current user is not allowed to execute
+ this action. Users may override this method."""
+
+ pass
+
+ def handle(self, args):
+
+ raise NotImplementedError
+
# vim: set filetype=python sts=4 sw=4 et si :