Sometimes, you may need to implement authentication with an XML-RPC server. This recipe presents an example of a basic HTTP authentication with an XML-RPC server.
We can create a subclass of SimpleXMLRPCServer
and override its request handler so that when a request comes, it is verified against a given login credentials.
Listing 8.3a gives the code for running an XML-RPC server with a basic HTTP authentication, as shown:
#!/usr/bin/env python # Python Network Programming Cookbook -- Chapter – 8 # This program is optimized for Python 2.7. # It may run on any other version with/without modifications. import argparse import xmlrpclib from base64 import b64decode from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler class SecureXMLRPCServer(SimpleXMLRPCServer): def __init__(self, host, port, username, password, *args, **kargs): self.username = username self.password = password # authenticate method is called from inner class class VerifyingRequestHandler(SimpleXMLRPCRequestHandler): # method to override def parse_request(request): if SimpleXMLRPCRequestHandler.parse_request(request): # authenticate if self.authenticate(request.headers): return True else: # if authentication fails return 401 request.send_error(401, 'Authentication failed ZZZ') return False # initialize SimpleXMLRPCServer.__init__(self, (host, port), requestHandler=VerifyingRequestHandler, *args, **kargs) def authenticate(self, headers): headers = headers.get('Authorization').split() basic, encoded = headers[0], headers[1] if basic != 'Basic': print 'Only basic authentication supported' return False secret = b64decode(encoded).split(':') username, password = secret[0], secret[1] return True if (username == self.username and password == self.password) else False def run_server(host, port, username, password): server = SecureXMLRPCServer(host, port, username, password) # simple test function def echo(msg): """Reply client in upper case """ reply = msg.upper() print "Client said: %s. So we echo that in uppercase: %s" %(msg, reply) return reply server.register_function(echo, 'echo') print "Running a HTTP auth enabled XMLRPC server on %s:%s..." %(host, port) server.serve_forever() if __name__ == '__main__': parser = argparse.ArgumentParser(description='Multithreaded multicall XMLRPC Server/Proxy') parser.add_argument('--host', action="store", dest="host", default='localhost') parser.add_argument('--port', action="store", dest="port", default=8000, type=int) parser.add_argument('--username', action="store", dest="username", default='user') parser.add_argument('--password', action="store", dest="password", default='pass') # parse arguments given_args = parser.parse_args() host, port = given_args.host, given_args.port username, password = given_args.username, given_args.password run_server(host, port, username, password)
If this server is run, then the following output can be seen by default:
$ python 8_3a_xmlrpc_server_with_http_auth.py Running a HTTP auth enabled XMLRPC server on localhost:8000... Client said: hello server.... So we echo that in uppercase: HELLO SERVER... localhost - - [27/Sep/2013 12:08:57] "POST /RPC2 HTTP/1.1" 200 -
Now, let us create a simple client proxy and use the same login credentials as used with the server.
Listing 8.3b gives the code for the XML-RPC Client, as shown:
#!/usr/bin/env python # Python Network Programming Cookbook -- Chapter – 8 # This program is optimized for Python 2.7. # It may run on any other version with/without modifications. import argparse import xmlrpclib def run_client(host, port, username, password): server = xmlrpclib.ServerProxy('http://%s:%s@%s:%s' %(username, password, host, port, )) msg = "hello server..." print "Sending message to server: %s " %msg print "Got reply: %s" %server.echo(msg) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Multithreaded multicall XMLRPC Server/Proxy') parser.add_argument('--host', action="store", dest="host", default='localhost') parser.add_argument('--port', action="store", dest="port", default=8000, type=int) parser.add_argument('--username', action="store", dest="username", default='user') parser.add_argument('--password', action="store", dest="password", default='pass') # parse arguments given_args = parser.parse_args() host, port = given_args.host, given_args.port username, password = given_args.username, given_args.password run_client(host, port, username, password)
If you run the client, then it shows the following output:
$ python 8_3b_xmprpc_client.py Sending message to server: hello server... Got reply: HELLO SERVER...
In the server script, the SecureXMLRPCServer
subclass is created by inheriting from SimpleXMLRPCServer
. In this subclass' initialization code, we created the VerifyingRequestHandler
class that actually intercepts the request and does the basic authentication using the
authenticate()
method.
In the authenticate()
method, the HTTP request is passed as an argument. This method checks the presence of the value of Authorization
. If its value is set to Basic
, it then decodes the encoded password with the b64decode()
function from the base64
standard module. After extracting the username and password, it then checks that with the server's given credentials set up initially.
In the run_server()
function, a simple echo()
subfunction is defined and registered with the SecureXMLRPCServer
instance.
In the client script, run_client()
simply takes the server address and login credentials and passes them to the ServerProxy()
instance. It then sends a single line message via the echo()
method.
18.117.11.247