Skip to content

Commit 303b9d4

Browse files
committed
* Refactor XMLRPC interface.
* Make it accessible through web-server.
1 parent b7e8b0b commit 303b9d4

File tree

3 files changed

+274
-196
lines changed

3 files changed

+274
-196
lines changed

roundup/cgi/client.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from roundup.cgi.form_parser import FormParser
1717
from roundup.mailer import Mailer, MessageSendError
1818
from roundup.cgi import accept_language
19+
from roundup import xmlrpc
1920

2021
def initialiseSecurity(security):
2122
'''Create some Permissions and Roles on the security object
@@ -354,10 +355,41 @@ def main(self):
354355
""" Wrap the real main in a try/finally so we always close off the db.
355356
"""
356357
try:
357-
self.inner_main()
358+
if self.env.get('CONTENT_TYPE') == 'text/xml':
359+
self.handle_xmlrpc()
360+
else:
361+
self.inner_main()
358362
finally:
359363
if hasattr(self, 'db'):
360364
self.db.close()
365+
366+
367+
def handle_xmlrpc(self):
368+
369+
# Pull the raw XML out of the form. The "value" attribute
370+
# will be the raw content of the POST request.
371+
assert self.form.file
372+
input = self.form.value
373+
# So that the rest of Roundup can query the form in the
374+
# usual way, we create an empty list of fields.
375+
self.form.list = []
376+
377+
# Set the charset and language, since other parts of
378+
# Roundup may depend upon that.
379+
self.determine_charset()
380+
self.determine_language()
381+
# Open the database as the correct user.
382+
self.determine_user()
383+
384+
# Call the appropriate XML-RPC method.
385+
handler = xmlrpc.RoundupDispatcher(self.db, self.userid, self.translator,
386+
allow_none=True)
387+
output = handler.dispatch(input)
388+
self.db.commit()
389+
390+
self.setHeader("Content-Type", "text/xml")
391+
self.setHeader("Content-Length", str(len(output)))
392+
self.write(output)
361393

362394
def inner_main(self):
363395
"""Process a request.

roundup/scripts/roundup_xmlrpc_server.py

Lines changed: 121 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,113 @@
55
# For license terms see the file COPYING.txt.
66
#
77

8-
import getopt, os, sys, socket
9-
from roundup.xmlrpc import RoundupServer, RoundupRequestHandler
8+
import base64, getopt, os, sys, socket, urllib
9+
from roundup.xmlrpc import translate
10+
from roundup.xmlrpc import RoundupInstance
11+
import roundup.instance
1012
from roundup.instance import TrackerError
13+
from roundup.cgi.exceptions import Unauthorised
1114
from SimpleXMLRPCServer import SimpleXMLRPCServer
15+
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
16+
17+
18+
class RequestHandler(SimpleXMLRPCRequestHandler):
19+
"""A SimpleXMLRPCRequestHandler with support for basic
20+
HTTP Authentication."""
21+
22+
TRACKER_HOMES = {}
23+
TRACKERS = {}
24+
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
29+
30+
def get_tracker(self, name):
31+
"""Return a tracker instance for given tracker name."""
32+
33+
if name in self.TRACKERS:
34+
return self.TRACKERS[name]
35+
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
42+
43+
44+
def authenticate(self, tracker):
45+
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)
51+
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
70+
71+
72+
def do_POST(self):
73+
"""Extract username and password from authorization header."""
74+
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)
81+
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()
95+
96+
97+
class Server(SimpleXMLRPCServer):
98+
99+
def _dispatch(self, method, params):
100+
101+
retn = SimpleXMLRPCServer._dispatch(self, method, params)
102+
retn = translate(retn)
103+
return retn
104+
12105

13106
def usage():
14-
print """
107+
print """Usage: %s: [options] [name=tracker home]+
15108
16109
Options:
17110
-i instance home -- specify the issue tracker "home directory" to administer
18111
-V -- be verbose when importing
19112
-p, --port <port> -- port to listen on
20113
21-
"""
114+
"""%sys.argv[0]
22115

23116
def run():
24117

@@ -30,38 +123,42 @@ def run():
30123
return 1
31124

32125
verbose = False
33-
tracker = ''
34126
port = 8000
35127
encoding = None
36128

37129
for opt, arg in opts:
38130
if opt == '-V':
39131
verbose = True
40-
elif opt == '-i':
41-
tracker = arg
42132
elif opt in ['-p', '--port']:
43133
port = int(arg)
44134
elif opt in ['-e', '--encoding']:
45135
encoding = encoding
46136

47-
if sys.version_info[0:2] < (2,5):
48-
if encoding:
49-
print 'encodings not supported with python < 2.5'
50-
sys.exit(-1)
51-
server = SimpleXMLRPCServer(('', port), RoundupRequestHandler)
52-
else:
53-
server = SimpleXMLRPCServer(('', port), RoundupRequestHandler,
54-
allow_none=True, encoding=encoding)
55-
if not os.path.exists(tracker):
56-
print 'Instance home does not exist.'
57-
sys.exit(-1)
58-
try:
59-
object = RoundupServer(tracker, verbose)
60-
except TrackerError:
61-
print 'Instance home does not exist.'
62-
sys.exit(-1)
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)
149+
150+
tracker_homes[name] = home
151+
152+
RequestHandler.TRACKER_HOMES=tracker_homes
63153

64-
server.register_instance(object)
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)
65162

66163
# Go into the main listener loop
67164
print 'Roundup XMLRPC server started on %s:%d' \

0 commit comments

Comments
 (0)