Index: roundup/exceptions.py =================================================================== --- roundup/exceptions.py (revision 4128) +++ roundup/exceptions.py (working copy) @@ -16,4 +16,7 @@ ''' pass +class UsageError(ValueError): + pass + # vim: set filetype=python ts=4 sw=4 et si Index: roundup/admin.py =================================================================== --- roundup/admin.py (revision 4128) +++ roundup/admin.py (working copy) @@ -29,6 +29,7 @@ import roundup.instance from roundup.configuration import CoreConfig from roundup.i18n import _ +from roundup.exception import UsageError class CommandDict(UserDict.UserDict): '''Simple dictionary that lets us do lookups using partial keys. @@ -49,9 +50,6 @@ raise KeyError, key return l -class UsageError(ValueError): - pass - class AdminTool: ''' A collection of methods used in maintaining Roundup trackers. Index: roundup/xmlrpc.py =================================================================== --- roundup/xmlrpc.py (revision 4128) +++ roundup/xmlrpc.py (working copy) @@ -5,12 +5,35 @@ # import base64 -import roundup.instance from roundup import hyperdb from roundup.cgi.exceptions import * -from roundup.admin import UsageError -from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler +from roundup.exceptions import UsageError +#from roundup import actions +from SimpleXMLRPCServer import * +def props_from_args(db, cl, args, itemid=None): + """Construct a list of properties from the given arguments, + and return them after validation.""" + + props = {} + for arg in args: + if arg.find('=') == -1: + raise UsageError, 'argument "%s" not propname=value'%arg + l = arg.split('=') + if len(l) < 2: + raise UsageError, 'argument "%s" not propname=value'%arg + key, value = l[0], '='.join(l[1:]) + if value: + try: + props[key] = hyperdb.rawToHyperdb(db, cl, itemid, + key, value) + except hyperdb.HyperdbValueError, message: + raise UsageError, message + else: + props[key] = None + + return props + class RoundupRequestHandler(SimpleXMLRPCRequestHandler): """A SimpleXMLRPCRequestHandler with support for basic HTTP Authentication.""" @@ -80,26 +103,9 @@ """Construct a list of properties from the given arguments, and return them after validation.""" - props = {} - for arg in args: - if arg.find('=') == -1: - raise UsageError, 'argument "%s" not propname=value'%arg - l = arg.split('=') - if len(l) < 2: - raise UsageError, 'argument "%s" not propname=value'%arg - key, value = l[0], '='.join(l[1:]) - if value: - try: - props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, - key, value) - except hyperdb.HyperdbValueError, message: - raise UsageError, message - else: - props[key] = None + return props_from_args(self.db, cl, args, itemid) - return props - #The server object class RoundupServer: """The RoundupServer provides the interface accessible through @@ -195,4 +201,107 @@ finally: r.close() -# vim: set et sts=4 sw=4 : + + +class RoundupServer2: + """As the above, but without authentication.""" + + def __init__(self, db, userid, translator): + + self.db = db + self.userid = userid + self.translator = translator + + def list(self, classname, propname=None): + cl = self.db.getclass(classname) + if not propname: + propname = cl.labelprop() + result = [cl.get(itemid, propname) + for itemid in cl.list() + if self.db.security.hasPermission('View', self.userid, + classname, propname, itemid) + ] + return result + + def filter(self, classname, search_matches, filterspec, + sort=[], group=[]): + cl = self.db.getclass(classname) + result = cl.filter(search_matches, filterspec, sort=sort, group=group) + return result + + def display(self, designator, *properties): + classname, itemid = hyperdb.splitDesignator(designator) + cl = self.db.getclass(classname) + props = properties and list(properties) or cl.properties.keys() + props.sort() + for p in props: + if not self.db.security.hasPermission('View', self.userid, + classname, p, itemid): + raise Unauthorised('Permission to view %s of %s denied'% + (p, designator)) + result = [(prop, cl.get(itemid, prop)) for prop in props] + return dict(result) + + def create(self, classname, *args): + if not self.db.security.hasPermission('Create', self.userid, classname): + raise Unauthorised('Permission to create %s denied'%classname) + + cl = self.db.getclass(classname) + + # convert types + props = props_from_args(self.db, cl, args) + + # check for the key property + key = cl.getkey() + if key and not props.has_key(key): + raise UsageError, 'you must provide the "%s" property.'%key + + # do the actual create + try: + result = cl.create(**props) + except (TypeError, IndexError, ValueError), message: + raise UsageError, message + return result + + def set(self, designator, *args): + + classname, itemid = hyperdb.splitDesignator(designator) + cl = self.db.getclass(classname) + props = props_from_args(self.db, cl, args, itemid) # convert types + for p in props.iterkeys(): + if not self.db.security.hasPermission('Edit', self.userid, + classname, p, itemid): + raise Unauthorised('Permission to edit %s of %s denied'% + (p, designator)) + try: + return cl.set(itemid, **props) + except (TypeError, IndexError, ValueError), message: + raise UsageError, message + + #actions = {'greet': actions.Greet} + + #def action(self, name, *args): + # """""" + # + # if name in self.actions: + # action = self.actions[name](self.db, self.userid, self.translator) + # return action(*args) + # else: + # raise Exception('action "%s" is not supported %s' % (name, ','.join(self.actions.keys()))) + + +class RoundupDispatcher(SimpleXMLRPCDispatcher): + """RoundupDispatcher bridges from cgi.client to RoundupServer2. + It expects user authentication to be done.""" + + def __init__(self, db, userid, translator, + allow_none=False, encoding=None): + + SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding) + self.register_instance(RoundupServer2(db, userid, translator)) + + + def dispatch(self, input): + + return self._marshaled_dispatch(input) + Index: roundup/cgi/client.py =================================================================== --- roundup/cgi/client.py (revision 4139) +++ roundup/cgi/client.py (working copy) @@ -16,6 +16,7 @@ from roundup.cgi.form_parser import FormParser from roundup.mailer import Mailer, MessageSendError from roundup.cgi import accept_language +from roundup import xmlrpc def initialiseSecurity(security): '''Create some Permissions and Roles on the security object @@ -354,11 +355,42 @@ ''' Wrap the real main in a try/finally so we always close off the db. ''' try: - self.inner_main() + if self.env.get('CONTENT_TYPE') == 'text/xml': + self.handle_xmlrpc() + else: + self.inner_main() finally: if hasattr(self, 'db'): self.db.close() + + def handle_xmlrpc(self): + + # Pull the raw XML out of the form. The "value" attribute + # will be the raw content of the POST request. + assert self.form.file + input = self.form.value + # So that the rest of Roundup can query the form in the + # usual way, we create an empty list of fields. + self.form.list = [] + + # Set the charset and language, since other parts of + # Roundup may depend upon that. + self.determine_charset() + self.determine_language() + # Open the database as the correct user. + self.determine_user() + + # Call the appropriate XML-RPC method. + handler = xmlrpc.RoundupDispatcher(self.db, self.userid, + allow_none=True) + output = handler.dispatch(input) + self.db.commit() + + self.setHeader("Content-Type", "text/xml") + self.setHeader("Content-Length", str(len(output))) + self.write(output) + def inner_main(self): '''Process a request.