Code

* Refactor 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             username, password = decoded.split(':')
55         if not username:
56             username = 'anonymous'
57         db = tracker.open('admin')
58         try:
59             userid = db.user.lookup(username)
60         except KeyError: # No such user
61             db.close()
62             raise Unauthorised, 'Invalid user'
63         stored = db.user.get(userid, 'password')
64         if stored != password:
65             # Wrong password
66             db.close()
67             raise Unauthorised, 'Invalid user'
68         db.setCurrentUser(username)
69         return db
72     def do_POST(self):
73         """Extract username and password from authorization header."""
75         db = None
76         try:
77             path = self.path.split('/')
78             tracker_name = urllib.unquote(path[1]).lower()
79             tracker = self.get_tracker(tracker_name)
80             db = self.authenticate(tracker)
82             instance = RoundupInstance(db, None)
83             self.server.register_instance(instance)
84             SimpleXMLRPCRequestHandler.do_POST(self)
85         except Unauthorised, message:
86             self.send_error(403, '%s (%s)'%(self.path, message))
87         except:
88             if db:
89                 db.close()
90             exc, val, tb = sys.exc_info()
91             print exc, val, tb
92             raise
93         if db:
94             db.close()
97 class Server(SimpleXMLRPCServer):
99     def _dispatch(self, method, params):
101         retn = SimpleXMLRPCServer._dispatch(self, method, params)
102         retn = translate(retn)
103         return retn
106 def usage():
107     print """Usage: %s: [options] [name=tracker home]+
109 Options:
110  -i instance home  -- specify the issue tracker "home directory" to administer
111  -V                -- be verbose when importing
112  -p, --port <port> -- port to listen on
114 """%sys.argv[0]
116 def run():
118     try:
119         opts, args = getopt.getopt(sys.argv[1:],
120                                    'e:i:p:V', ['encoding=', 'port='])
121     except getopt.GetoptError, e:
122         usage()
123         return 1
125     verbose = False
126     port = 8000
127     encoding = None
129     for opt, arg in opts:
130         if opt == '-V':
131             verbose = True
132         elif opt in ['-p', '--port']:
133             port = int(arg)
134         elif opt in ['-e', '--encoding']:
135             encoding = encoding
137     tracker_homes = {}
138     for arg in args:
139         try:
140             name, home = arg.split('=', 1)
141             # Validate the argument
142             tracker = roundup.instance.open(home)
143         except ValueError:
144             print 'Instances must be name=home'
145             sys.exit(-1)
146         except TrackerError:
147             print 'Tracker home does not exist.'
148             sys.exit(-1)
150         tracker_homes[name] = home
152     RequestHandler.TRACKER_HOMES=tracker_homes
154     if sys.version_info[0:2] < (2,5):
155         if encoding:
156             print 'encodings not supported with python < 2.5'
157             sys.exit(-1)
158         server = Server(('', port), RequestHandler)
159     else:
160         server = Server(('', port), RequestHandler,
161                         allow_none=True, encoding=encoding)
163     # Go into the main listener loop
164     print 'Roundup XMLRPC server started on %s:%d' \
165           % (socket.gethostname(), port)
166     try:
167         server.serve_forever()
168     except KeyboardInterrupt:
169         print 'Keyboard Interrupt: exiting'
171 if __name__ == '__main__':
172     run()