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):
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 -e, --encoding -- specify the encoding to use
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()