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 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()