Code

* Add support for actions to XMLRPC interface.
[roundup.git] / roundup / scripts / roundup_xmlrpc_server.py
1 #! /usr/bin/env python
2 #
3 # Copyright (C) 2007 Stefan Seefeld
4 # All rights reserved.
5 # For license terms see the file COPYING.txt.
6 #
8 import base64, getopt, os, sys, socket, urllib
9 from roundup.xmlrpc import translate
10 from roundup.xmlrpc import RoundupInstance
11 import roundup.instance
12 from roundup.instance import TrackerError
13 from roundup.cgi.exceptions import Unauthorised
14 from SimpleXMLRPCServer import SimpleXMLRPCServer
15 from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
18 class RequestHandler(SimpleXMLRPCRequestHandler):
19     """A SimpleXMLRPCRequestHandler with support for basic
20     HTTP Authentication."""
22     TRACKER_HOMES = {}
23     TRACKERS = {}
25     def is_rpc_path_valid(self):
26         path = self.path.split('/')
27         name = urllib.unquote(path[1]).lower()
28         return name in self.TRACKER_HOMES
30     def get_tracker(self, name):
31         """Return a tracker instance for given tracker name."""
33         if name in self.TRACKERS:
34             return self.TRACKERS[name]
36         if name not in self.TRACKER_HOMES:
37             raise Exception('No such tracker "%s"'%name)
38         tracker_home = self.TRACKER_HOMES[name]
39         tracker = roundup.instance.open(tracker_home)
40         self.TRACKERS[name] = tracker
41         return tracker
44     def authenticate(self, tracker):
46         
47         # Try to extract username and password from HTTP Authentication.
48         username, password = None, None
49         authorization = self.headers.get('authorization', ' ')
50         scheme, challenge = authorization.split(' ', 1)
52         if scheme.lower() == 'basic':
53             decoded = base64.decodestring(challenge)
54             if ':' in decoded:
55                 username, password = decoded.split(':')
56             else:
57                 username = decoded
58         if not username:
59             username = 'anonymous'
60         db = tracker.open('admin')
61         try:
62             userid = db.user.lookup(username)
63         except KeyError: # No such user
64             db.close()
65             raise Unauthorised, 'Invalid user'
66         stored = db.user.get(userid, 'password')
67         if stored != password:
68             # Wrong password
69             db.close()
70             raise Unauthorised, 'Invalid user'
71         db.setCurrentUser(username)
72         return db
75     def do_POST(self):
76         """Extract username and password from authorization header."""
78         db = None
79         try:
80             path = self.path.split('/')
81             tracker_name = urllib.unquote(path[1]).lower()
82             tracker = self.get_tracker(tracker_name)
83             db = self.authenticate(tracker)
85             instance = RoundupInstance(db, tracker.actions, None)
86             self.server.register_instance(instance)
87             SimpleXMLRPCRequestHandler.do_POST(self)
88         except Unauthorised, message:
89             self.send_error(403, '%s (%s)'%(self.path, message))
90         except:
91             if db:
92                 db.close()
93             exc, val, tb = sys.exc_info()
94             print exc, val, tb
95             raise
96         if db:
97             db.close()
100 class Server(SimpleXMLRPCServer):
102     def _dispatch(self, method, params):
104         retn = SimpleXMLRPCServer._dispatch(self, method, params)
105         retn = translate(retn)
106         return retn
109 def usage():
110     print """Usage: %s: [options] [name=tracker home]+
112 Options:
113  -i instance home  -- specify the issue tracker "home directory" to administer
114  -V                -- be verbose when importing
115  -p, --port <port> -- port to listen on
117 """%sys.argv[0]
119 def run():
121     try:
122         opts, args = getopt.getopt(sys.argv[1:],
123                                    'e:i:p:V', ['encoding=', 'port='])
124     except getopt.GetoptError, e:
125         usage()
126         return 1
128     verbose = False
129     port = 8000
130     encoding = None
132     for opt, arg in opts:
133         if opt == '-V':
134             verbose = True
135         elif opt in ['-p', '--port']:
136             port = int(arg)
137         elif opt in ['-e', '--encoding']:
138             encoding = encoding
140     tracker_homes = {}
141     for arg in args:
142         try:
143             name, home = arg.split('=', 1)
144             # Validate the argument
145             tracker = roundup.instance.open(home)
146         except ValueError:
147             print 'Instances must be name=home'
148             sys.exit(-1)
149         except TrackerError:
150             print 'Tracker home does not exist.'
151             sys.exit(-1)
153         tracker_homes[name] = home
155     RequestHandler.TRACKER_HOMES=tracker_homes
157     if sys.version_info[0:2] < (2,5):
158         if encoding:
159             print 'encodings not supported with python < 2.5'
160             sys.exit(-1)
161         server = Server(('', port), RequestHandler)
162     else:
163         server = Server(('', port), RequestHandler,
164                         allow_none=True, encoding=encoding)
166     # Go into the main listener loop
167     print 'Roundup XMLRPC server started on %s:%d' \
168           % (socket.gethostname(), port)
169     try:
170         server.serve_forever()
171     except KeyboardInterrupt:
172         print 'Keyboard Interrupt: exiting'
174 if __name__ == '__main__':
175     run()