Fotinakis.com Blog


Python XML-RPC Client with Cookie Handling and SSL

March 9, 2008


The following is an adaptation of Vaibhav Bhatia's cookie-handling XML-RPC client (text source) with a few changes to implement SSL for HTTPS transport in place of the basic HTTP transport.

This was done specifically for use with CherryPy sessions so that the XML-RPC client (the server proxy object) could be stored in a session variable, giving each user an individual client and thus allowing webservice permissions to be handled on a per-user basis (without having to authenticate and determine permissions on every webservice method invocation). To do this, make sure that you're storing CherryPy sessions in memory, not in a file, and then create the proxy object (sorry for the gigantic class name):

cherrypy.session['proxy'] = \
	xmlrpclib.ServerProxy(url, transport=CookieAuthXMLRPCSafeTransport())

xmlrpcclient.py

import os
import base64
import xmlrpclib
import urllib2
import cookielib
 
class CookieAuthXMLRPCSafeTransport(xmlrpclib.SafeTransport):
    """ xmlrpclib.Transport that sends HTTPS Authentication"""
 
    user_agent = '*py*'
    credentials = ()
    cookiefile = 'cookies.lwp'
 
    def send_basic_auth(self, connection):
        """Include HTTPS Authentication data in a header"""
 
        auth = base64.encodestring("%s:%s"%self.credentials).strip()
        auth = 'Basic %s' %(auth,)
        connection.putheader('Authorization',auth)
 
    def send_cookie_auth(self, connection):
        """Include Cookie Authentication data in a header"""
 
        cj = cookielib.LWPCookieJar()
        cj.load(self.cookiefile)
 
        for cookie in cj:
            if cookie.name == 'session_id':
                uuidstr = cookie.value
            connection.putheader("Cookie",cookie.name+'='+cookie.value)
 
    ## override the send_host hook to also send authentication info
    def send_host(self, connection, host):
        xmlrpclib.SafeTransport.send_host(self, connection, host)
        if os.path.exists(self.cookiefile):
            self.send_cookie_auth(connection)
        elif self.credentials != ():
            self.send_basic_auth(connection)
 
    def request(self, host, handler, request_body, verbose=0):
        # dummy request class for extracting cookies
        class CookieRequest(urllib2.Request):
            pass
 
        # dummy response class for extracting cookies
        class CookieResponse:
            def __init__(self, headers):
                self.headers = headers
            def info(self):
                return self.headers 
 
        crequest = CookieRequest('https://'+host+'/')
 
        # issue XML-RPC request
        h = self.make_connection(host)
        if verbose:
            h.set_debuglevel(1)
 
        self.send_request(h, handler, request_body)
        self.send_host(h, host)
        self.send_user_agent(h)
 
        # creating a cookie jar for my cookies
        cj = cookielib.LWPCookieJar()
 
        self.send_content(h, request_body)
 
        errcode, errmsg, headers = h.getreply()
 
        cresponse = CookieResponse(headers)
        cj.extract_cookies(cresponse, crequest)
 
        if len(cj) > 0 and self.cookiefile != None:
            cj.save(self.cookiefile)
 
        if errcode != 200:
            raise xmlrpclib.ProtocolError(
                host + handler,
                errcode, errmsg,
                headers
                )
 
        self.verbose = verbose
 
        return self.parse_response(h.getfile())

Leave a Reply