Welcome, guest | Sign In | My Account | Store | Cart

These classes subclass several of the culprits in twisted.web.xmlrpc that are responsible for not being able to make autenticated XML-RPC calls.

Note that this recipe also makes use of the URI classes that were posted in a previous recipe here: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473864

I will submit a patch to twisted along these lines, but there will be no references to the custom Uri module or code.

Update: XML-RPC Authentication is now supported in Twisted (version 2.4 included a patch based on this recipe). If you are using a recent version of Twisted or can update to a recent version, this recipe is no longer needed.

Python, 91 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import base64
import xmlrpclib

from twisted.web import xmlrpc
from twisted.internet import reactor, defer

from where.you.installed.uri import Uri

class AuthQueryProtocol(xmlrpc.QueryProtocol):
    '''
    We're just over-riding the connectionMade() method so that we
    can add the Authorization header.
    '''
    def connectionMade(self):
        self.sendCommand('POST', self.factory.url)
        self.sendHeader('User-Agent', 'Twisted/XMLRPClib')
        self.sendHeader('Host', self.factory.host)
        self.sendHeader('Content-type', 'text/xml')
        self.sendHeader('Content-length', 
            str(len(self.factory.payload)))
        if self.factory.user:
            auth = base64.encodestring('%s:%s' % (
                self.factory.user, self.factory.password))
            self.sendHeader('Authorization', 'Basic %s' % auth)
        self.endHeaders()
        self.transport.write(self.factory.payload)

class AuthQueryFactory(xmlrpc.QueryFactory):
    '''
    We're using a Uri object here for the url, diverging pretty
    strongly from how it's done in t.w.xmlrpc. This is done for 
    convenience and simplicity of presentation in this recipe.
    '''
    deferred = None
    protocol = AuthQueryProtocol

    def __init__(self, url, method, *args):
        self.url, self.host = url.path, url.host
        self.user, self.password = url.user, url.password
        self.payload = xmlrpc.payloadTemplate % (
            method, xmlrpclib.dumps(args))
        self.deferred = defer.Deferred()

class AuthProxy:
    '''
    A Proxy for making remote XML-RPC calls that supports Basic
    Authentication. There's no sense subclassing this, since it needs
    to override all of xmlrpc.Proxy.

    Pass the URL of the remote XML-RPC server to the constructor.

    Use proxy.callRemote('foobar', *args) to call remote method
    'foobar' with *args.
    '''
    def __init__(self, url):
        self.url = Uri(url)
        self.host = self.url.host
        self.port = self.url.port
        self.secure = self.url.scheme == 'https'

    def callRemote(self, method, *args):
        factory = AuthQueryFactory(self.url, method, *args)
        if self.secure:
            from twisted.internet import ssl
            reactor.connectSSL(self.host, self.port or 443,
                               factory, ssl.ClientContextFactory())
        else:
            reactor.connectTCP(self.host, self.port or 80, factory)
        return factory.deferred

def _test():
    '''
    In this test, we'll hit a custom Zope/Plone script on a local Zope server.

    The output will give us this:
    [('authenticated', True), ('name', 'admin'), ('roles', ['Manager', 'Authenticated']), ('id', 'admin')]
    '''
    def callback(data):
        print data.items()
        reactor.stop()
    def errback(failure):
        #print failure.getErrorMessage()
        reactor.stop()
    server = AuthProxy('http://admin:admin@192.168.4.10:9673/site01/')
    d = server.callRemote('getXMLRPCUserInfo')
    d.addCallback(callback)
    d.addErrback(callback)
    reactor.run()

if __name__ == '__main__':
    _test()

Use it like it shows in _test() ;-)

3 comments

fairwinds 14 years, 8 months ago  # | flag

Twisted xmlrpc auth. Hi I am looking for this in Twisted 2.5 and can't seem to find it. Twisted is a large package so perhaps I am just not finding it. Can you advise the location of the code in twisted. Many thanks.

Graeme Glass 13 years, 7 months ago  # | flag

rename QueryFactory to _QueryFactory. Nice Recipe thanks. In Twisted 2.5.0 it seems that QueryFactory is not in twisted.web.xmlrpc, but _QueryFactory is. Renaming it in your script fixes it.

Thanks for the recipe.

Graeme Glass 13 years, 7 months ago  # | flag

and now I see why. Never mind, ignore that post.