summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: ebf52fc)
raw | patch | inline | side by side (parent: ebf52fc)
author | stefan <stefan@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Wed, 25 Feb 2009 18:17:39 +0000 (18:17 +0000) | ||
committer | stefan <stefan@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Wed, 25 Feb 2009 18:17:39 +0000 (18:17 +0000) |
* Make it accessible through web-server.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/roundup/trunk@4171 57a73879-2fb5-44c3-a270-3262357dd7e2
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/roundup/trunk@4171 57a73879-2fb5-44c3-a270-3262357dd7e2
roundup/cgi/client.py | patch | blob | history | |
roundup/scripts/roundup_xmlrpc_server.py | patch | blob | history | |
roundup/xmlrpc.py | patch | blob | history |
diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 7d534ca7563752a2126a8948f28d0ca6679386f6..0ea02f13388ea04c5bbde32a1ba79f160730f570 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
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
""" 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, self.translator,
+ 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.
index 020cc3805c8e9c0b7f7949316d47c4b228ef623a..44c626f80c55c19db0367daee285dba8418b7666 100644 (file)
# For license terms see the file COPYING.txt.
#
-import getopt, os, sys, socket
-from roundup.xmlrpc import RoundupServer, RoundupRequestHandler
+import base64, getopt, os, sys, socket, urllib
+from roundup.xmlrpc import translate
+from roundup.xmlrpc import RoundupInstance
+import roundup.instance
from roundup.instance import TrackerError
+from roundup.cgi.exceptions import Unauthorised
from SimpleXMLRPCServer import SimpleXMLRPCServer
+from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
+
+
+class RequestHandler(SimpleXMLRPCRequestHandler):
+ """A SimpleXMLRPCRequestHandler with support for basic
+ HTTP Authentication."""
+
+ TRACKER_HOMES = {}
+ TRACKERS = {}
+
+ def is_rpc_path_valid(self):
+ path = self.path.split('/')
+ name = urllib.unquote(path[1]).lower()
+ return name in self.TRACKER_HOMES
+
+ def get_tracker(self, name):
+ """Return a tracker instance for given tracker name."""
+
+ if name in self.TRACKERS:
+ return self.TRACKERS[name]
+
+ if name not in self.TRACKER_HOMES:
+ raise Exception('No such tracker "%s"'%name)
+ tracker_home = self.TRACKER_HOMES[name]
+ tracker = roundup.instance.open(tracker_home)
+ self.TRACKERS[name] = tracker
+ return tracker
+
+
+ def authenticate(self, tracker):
+
+
+ # Try to extract username and password from HTTP Authentication.
+ username, password = None, None
+ authorization = self.headers.get('authorization', ' ')
+ scheme, challenge = authorization.split(' ', 1)
+
+ if scheme.lower() == 'basic':
+ decoded = base64.decodestring(challenge)
+ username, password = decoded.split(':')
+ if not username:
+ username = 'anonymous'
+ db = tracker.open('admin')
+ try:
+ userid = db.user.lookup(username)
+ except KeyError: # No such user
+ db.close()
+ raise Unauthorised, 'Invalid user'
+ stored = db.user.get(userid, 'password')
+ if stored != password:
+ # Wrong password
+ db.close()
+ raise Unauthorised, 'Invalid user'
+ db.setCurrentUser(username)
+ return db
+
+
+ def do_POST(self):
+ """Extract username and password from authorization header."""
+
+ db = None
+ try:
+ path = self.path.split('/')
+ tracker_name = urllib.unquote(path[1]).lower()
+ tracker = self.get_tracker(tracker_name)
+ db = self.authenticate(tracker)
+
+ instance = RoundupInstance(db, None)
+ self.server.register_instance(instance)
+ SimpleXMLRPCRequestHandler.do_POST(self)
+ except Unauthorised, message:
+ self.send_error(403, '%s (%s)'%(self.path, message))
+ except:
+ if db:
+ db.close()
+ exc, val, tb = sys.exc_info()
+ print exc, val, tb
+ raise
+ if db:
+ db.close()
+
+
+class Server(SimpleXMLRPCServer):
+
+ def _dispatch(self, method, params):
+
+ retn = SimpleXMLRPCServer._dispatch(self, method, params)
+ retn = translate(retn)
+ return retn
+
def usage():
- print """
+ print """Usage: %s: [options] [name=tracker home]+
Options:
-i instance home -- specify the issue tracker "home directory" to administer
-V -- be verbose when importing
-p, --port <port> -- port to listen on
-"""
+"""%sys.argv[0]
def run():
return 1
verbose = False
- tracker = ''
port = 8000
encoding = None
for opt, arg in opts:
if opt == '-V':
verbose = True
- elif opt == '-i':
- tracker = arg
elif opt in ['-p', '--port']:
port = int(arg)
elif opt in ['-e', '--encoding']:
encoding = encoding
- if sys.version_info[0:2] < (2,5):
- if encoding:
- print 'encodings not supported with python < 2.5'
- sys.exit(-1)
- server = SimpleXMLRPCServer(('', port), RoundupRequestHandler)
- else:
- server = SimpleXMLRPCServer(('', port), RoundupRequestHandler,
- allow_none=True, encoding=encoding)
- if not os.path.exists(tracker):
- print 'Instance home does not exist.'
- sys.exit(-1)
- try:
- object = RoundupServer(tracker, verbose)
- except TrackerError:
- print 'Instance home does not exist.'
- sys.exit(-1)
+ tracker_homes = {}
+ for arg in args:
+ try:
+ name, home = arg.split('=', 1)
+ # Validate the argument
+ tracker = roundup.instance.open(home)
+ except ValueError:
+ print 'Instances must be name=home'
+ sys.exit(-1)
+ except TrackerError:
+ print 'Tracker home does not exist.'
+ sys.exit(-1)
+
+ tracker_homes[name] = home
+
+ RequestHandler.TRACKER_HOMES=tracker_homes
- server.register_instance(object)
+ if sys.version_info[0:2] < (2,5):
+ if encoding:
+ print 'encodings not supported with python < 2.5'
+ sys.exit(-1)
+ server = Server(('', port), RequestHandler)
+ else:
+ server = Server(('', port), RequestHandler,
+ allow_none=True, encoding=encoding)
# Go into the main listener loop
print 'Roundup XMLRPC server started on %s:%d' \
diff --git a/roundup/xmlrpc.py b/roundup/xmlrpc.py
index c3eaccdcab8097c5afd9b5dd7edaf947eaad0c5d..45552872576c75a94f200567e1ca2f5d961d6d1b 100644 (file)
--- a/roundup/xmlrpc.py
+++ b/roundup/xmlrpc.py
# For license terms see the file COPYING.txt.
#
-import base64
-import roundup.instance
from roundup import hyperdb
from roundup.cgi.exceptions import *
-from roundup.admin import UsageError
-from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
-
-class RoundupRequestHandler(SimpleXMLRPCRequestHandler):
- """A SimpleXMLRPCRequestHandler with support for basic
- HTTP Authentication."""
-
- def do_POST(self):
- """Extract username and password from authorization header."""
-
- # Try to extract username and password from HTTP Authentication.
- self.username = None
- self.password = None
- authorization = self.headers.get('authorization', ' ')
- scheme, challenge = authorization.split(' ', 1)
-
- if scheme.lower() == 'basic':
- decoded = base64.decodestring(challenge)
- self.username, self.password = decoded.split(':')
-
- SimpleXMLRPCRequestHandler.do_POST(self)
+from roundup.exceptions import UsageError
+from roundup.date import Date, Range, Interval
+from SimpleXMLRPCServer import *
+
+def translate(value):
+ """Translate value to becomes valid for XMLRPC transmission."""
+
+ if isinstance(value, (Date, Range, Interval)):
+ return repr(value)
+ elif type(value) is list:
+ return [translate(v) for v in value]
+ elif type(value) is tuple:
+ return tuple([translate(v) for v in value])
+ elif type(value) is dict:
+ return dict([[translate(k), translate(value[k])] for k in value])
+ else:
+ return value
+
+
+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 RoundupInstance:
+ """The RoundupInstance provides the interface accessible through
+ the Python XMLRPC mapping."""
+
+ def __init__(self, db, translator):
+
+ self.db = db
+ self.userid = db.getuid()
+ 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.db.getuid(),
+ classname, propname, itemid)
+ ]
+ return result
- def _dispatch(self, method, params):
- """Inject username and password into function arguments."""
+ 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
- # Add username and password to function arguments
- params = [self.username, self.password] + list(params)
- return self.server._dispatch(method, params)
+ 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.db.getuid(),
+ 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.db.getuid(), classname):
+ raise Unauthorised('Permission to create %s denied'%classname)
-class RoundupRequest:
- """Little helper class to handle common per-request tasks such
- as authentication and login."""
+ cl = self.db.getclass(classname)
- def __init__(self, tracker, username, password):
- """Open the database for the given tracker, using the given
- username and password."""
+ # convert types
+ props = props_from_args(self.db, cl, args)
- self.tracker = tracker
- self.db = self.tracker.open('admin')
- try:
- self.userid = self.db.user.lookup(username)
- except KeyError: # No such user
- self.db.close()
- raise Unauthorised, 'Invalid user'
- stored = self.db.user.get(self.userid, 'password')
- if stored != password:
- # Wrong password
- self.db.close()
- raise Unauthorised, 'Invalid user'
- self.db.setCurrentUser(username)
-
- def close(self):
- """Close the database, after committing any changes, if needed."""
+ # 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:
- self.db.commit()
- finally:
- self.db.close()
-
- def get_class(self, classname):
- """Return the class for the given classname."""
-
- try:
- return self.db.getclass(classname)
- except KeyError:
- raise UsageError, 'no such class "%s"'%classname
-
- def props_from_args(self, 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(self.db, cl, itemid,
- key, value)
- except hyperdb.HyperdbValueError, message:
- raise UsageError, message
- else:
- props[key] = None
-
- return props
-
-
-#The server object
-class RoundupServer:
- """The RoundupServer provides the interface accessible through
- the Python XMLRPC mapping. All methods take an additional username
- and password argument so each request can be authenticated."""
-
- def __init__(self, tracker, verbose = False):
- self.tracker = roundup.instance.open(tracker)
- self.verbose = verbose
-
- def list(self, username, password, classname, propname=None):
- r = RoundupRequest(self.tracker, username, password)
- try:
- cl = r.get_class(classname)
- if not propname:
- propname = cl.labelprop()
- result = [cl.get(itemid, propname)
- for itemid in cl.list()
- if r.db.security.hasPermission('View', r.userid,
- classname, propname, itemid)
- ]
- finally:
- r.close()
+ result = cl.create(**props)
+ except (TypeError, IndexError, ValueError), message:
+ raise UsageError, message
return result
- def filter(self, username, password, classname, search_matches, filterspec,
- sort=[], group=[]):
- r = RoundupRequest(self.tracker, username, password)
- try:
- cl = r.get_class(classname)
- result = cl.filter(search_matches, filterspec, sort=sort, group=group)
- finally:
- r.close()
- return result
+ def set(self, designator, *args):
- def display(self, username, password, designator, *properties):
- r = RoundupRequest(self.tracker, username, password)
+ 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.db.getuid(),
+ classname, p, itemid):
+ raise Unauthorised('Permission to edit %s of %s denied'%
+ (p, designator))
try:
- classname, itemid = hyperdb.splitDesignator(designator)
- cl = r.get_class(classname)
- props = properties and list(properties) or cl.properties.keys()
- props.sort()
- for p in props:
- if not r.db.security.hasPermission('View', r.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]
- finally:
- r.close()
- return dict(result)
+ return cl.set(itemid, **props)
+ except (TypeError, IndexError, ValueError), message:
+ raise UsageError, message
- def create(self, username, password, classname, *args):
- r = RoundupRequest(self.tracker, username, password)
- try:
- if not r.db.security.hasPermission('Create', r.userid, classname):
- raise Unauthorised('Permission to create %s denied'%classname)
- cl = r.get_class(classname)
+class RoundupDispatcher(SimpleXMLRPCDispatcher):
+ """RoundupDispatcher bridges from cgi.client to RoundupInstance.
+ It expects user authentication to be done."""
- # convert types
- props = r.props_from_args(cl, args)
+ def __init__(self, db, userid, translator,
+ allow_none=False, encoding=None):
- # 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
+ SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
+ self.register_instance(RoundupInstance(db, userid, translator))
+
- # do the actual create
- try:
- result = cl.create(**props)
- except (TypeError, IndexError, ValueError), message:
- raise UsageError, message
- finally:
- r.close()
- return result
+ def dispatch(self, input):
+ return self._marshaled_dispatch(input)
- def set(self, username, password, designator, *args):
- r = RoundupRequest(self.tracker, username, password)
- try:
- classname, itemid = hyperdb.splitDesignator(designator)
- cl = r.get_class(classname)
- props = r.props_from_args(cl, args, itemid) # convert types
- for p in props.iterkeys ():
- if not r.db.security.hasPermission('Edit', r.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
- finally:
- r.close()
+ def _dispatch(self, method, params):
-# vim: set et sts=4 sw=4 :
+ retn = SimpleXMLRPCDispatcher._dispatch(self, method, params)
+ retn = translate(retn)
+ return retn
+