#!/usr/bin/env
'''Xmlrpc server with SSL and configurable authentication plugin method'''
from base64 import b64decode
import BaseHTTPServer
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler, SimpleXMLRPCDispatcher
import SocketServer
import socket
import ssl
import httplib
[docs]class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
'''Provides a ssl secured handler class for xmlrpc requests
'''
[docs] def setup(self):
'''Perform prior base class initializations
'''
self.connection = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
def __init__(self, req, addr, server):
SimpleXMLRPCRequestHandler.__init__(self, req, addr, server)
[docs] def do_POST(self):
'''Send POST responses with proper xml content
'''
try:
data = self.rfile.read(int(self.headers["content-length"]))
response = self.server._marshaled_dispatch(
data,
getattr(self, '_dispatch', None)
)
except Exception, post_exception:
self.send_response(httplib.INTERNAL_SERVER_ERROR)
self.end_headers()
raise post_exception
else:
self.send_response(httplib.OK)
self.send_header("Content-type", "text/xml")
self.send_header("Content-length", str(len(response)))
self.end_headers()
self.wfile.write(response)
self.wfile.flush()
[docs]class BaseRequestHandler(SecureXMLRPCRequestHandler):
'''Base Handler providing methods to handle xmlrpc incoming requests
'''
[docs] def parse_request(self):
'''Parses incoming requests and perform user authentication
'''
if SimpleXMLRPCRequestHandler.parse_request(self):
ret, error_msg, error_code = self.authenticate(self.headers)
if ret:
return True
else:
self.send_error(error_code, error_msg)
return False
[docs] def authenticate(self, headers):
'''Performs user authentication
Arguments:
headers (:obj:`str`): http/https headers received from client
Returns:
ret (:obj:`bool`): True if user successfully authenticated, False otherwise
Returns:
error_msg (:obj:`str`): Error message if authentication failed, None otherwise
Returns:
error_code (:obj:`str`): Error code if authentication failed, None otherwise
'''
auth_header = headers.get('Authorization')
if not auth_header:
return False, 'No authentication header provided in https request field',\
httplib.UNAUTHORIZED
(basic, _, encoded) = auth_header.partition(' ')
assert basic == 'Basic', 'Only basic authentication supported'
(username, _, password) = b64decode(encoded).partition(':')
if (not username) or (not password):
return False, 'Authentication field in https request not well formed',\
httplib.BAD_REQUEST
self.opt_args.update({'username': username, 'password': password})
ret, error_msg, error_code = self.verify_user_credentials()
if ret:
return True, None, None
else:
return False, error_msg, error_code
[docs] def verify_user_credentials(self): # pylint: disable=no-self-use
'''Verify the user credentials
Returns:
ret (:obj:`bool`): True if user successfully authenticated, False otherwise
Returns:
error_msg (:obj:`str`): Error message if authentication failed, None otherwise
Returns:
error_code (:obj:`str`): Error code if authentication failed, None otherwise
OBS: Must be overwritten with a proper authentication method in the child class
'''
return False, 'Error: no method provided to verify user credentials', httplib.BAD_REQUEST
[docs]class SecureAuthenticatedXMLRPCServer(BaseHTTPServer.HTTPServer, SocketServer.BaseServer,
SimpleXMLRPCDispatcher):
#class SecureAuthenticatedXMLRPCServer(SocketServer.BaseServer,
# SimpleXMLRPCDispatcher):
''' Xmlrpc server secured with ssl
Arguments:
server_address(:obj:`str`): ip address of the xmlrpc server
keyfile(:obj:`str`): path of the ssl/tls private keyfile generated for the xmlrpc server
certfile(:obj:`str`): path of the ssl/tls certificate file signed by the Certification
Authority
Keyword Arguments:
log_requests(:obj:`str`,optional, *default* =True): enable log all requests
path(:obj:`str`,optional, *default* ='/'): server http path
RequestHandler(:obj:`class`,optional, *default* =BaseRequestHandler): class to handle
client requests
ssl_version(:obj:`int`, optional, *default* = ssl.PROTOCOL_TLSv1 ): ssl protocol
version code
'''
def __init__(self, server_address, keyfile, certfile, **kwargs): # pylint: disable=super-init-not-called
default_args = {
'log_requests': True,
'path': "/",
'RequestHandler': BaseRequestHandler,
'ssl_version': ssl.PROTOCOL_TLSv1 # pylint: disable=no-member
}
default_args.update(kwargs)
self.logRequests = default_args['log_requests'] # pylint: disable=invalid-name
self.paths = default_args['path']
default_args['RequestHandler'].opt_args = default_args
try:
SimpleXMLRPCDispatcher.__init__(self)
except TypeError:
# fix for python > 2.5
SimpleXMLRPCDispatcher.__init__(self, False, None)
SocketServer.BaseServer.opt_args = kwargs
SocketServer.BaseServer.__init__(self, server_address, default_args['RequestHandler'])
self.socket = ssl.wrap_socket(socket.socket(self.address_family, self.socket_type),
server_side=True,
certfile=certfile,
keyfile=keyfile,
ssl_version=default_args['ssl_version']
)
self.server_bind()
self.server_activate()