Les codes d'erreurs
XML-RPC ne gère qu'une exception générique. De manière à récupérer les différents types d'exception, l'api de communiquation avec le bakend renvoie en premier des codes d'erreur, en général :
[0,[exception],[msg]]: en cas d'erreur [1, [resultat]]: si tout va bien
Le transport xml-rpc
Le backend encode les paramètres des méthodes invoquées, ainsi
que le retour, le résultat des méthodes, en UTF8. Pour pouvoir
localliser les données (en encoding UTF-8), il y a des deux côtés
des convertisseurs. Côté frontend, pour envoyer des paramètres de
méthode xml-rpc, il faut toujours les faire passer par u()
,
qui convertit en UTF8. La fonction convert()
a l'effet
inverse, elle permet donc de convertir les données transitées en
xml-rpc en encoding utf-8.
définir des utilisateurs authentifiés pour le serveur XML-RPC de twisted
La méthode retenue pour sécuriser xml-rpc est d'authentifier l'utilisateur par l'intermédiaire des requêtes http. Par exemple, la connexion au serveur se fera par l'appel suivant :
https://toto:password@192.168.230.63:7080/
Dans cet exemple, la librairie http va se charger de crypter le login et le mot de passe. du côté du serveur, les valeurs corespondantes peuvent être récupérées à travers l'objet requête comme suit :
cred_user = request.getUser() cred_password = request.getPassword()
La solution actuelle consiste à redéfinir les fonctions render() et __init__() de la classe XMLRPC de twisted (fichier /usr/lib/pythonX.X/site-package/twisted/web/xmlrpc). Dans la fonction __init__, on va définir les utilisateurs permis et leurs mot de passe (la version actuelle va chercher les mots de passes dans un annuaire ldap). On définit également des groupes de fonctions autorisés ou non (stockés dans la base de données) qui définissent quelles fonctions l'utilisateur a le droit d'exécuter sur le serveur.
Vérification de l'authentification des utilisateurs et interdiction de fonctions
Tous les utilisateurs présents dans l'annuaire peuvent être utilisés, mais il faut trouver un moyen de les vérifier et d'empêcher l'exécution de code non autorisé. l'implémentation de ces fonctions se fait au niveau de la fonction render() du serveur, qui est la fonction qui exécute la fonction demandée et retourne le résultat à l'utilisateur. Comme la requête HTTP est accessible dans cette fonction, il suffit de récupérer le login et le mot de passe envoyés dans cette requête et d'effectuer une connexion authentifiée (bind) à l'annuaire. Si la fonction échoue c'est que le mot de passe est mauvais. Dans le cas ou le nom de login et le mot de passe fournis sont corrects, on peut récupérer le nom de la fonction demandée par l'utilisateur via la fonction suivante:
arguments, nom_fonction = xmlrpclib.loads(request.content.read())
Une fois le nom de la fonction récupérée, on regarde si la fonction est dans les groupes autorisés pour cet utilisateur:
# cred_user est le login récupéré dans la requête # on vérifie si l'utilisateur a le droit d'utiliser cette fonction # on récupère les groupes de droits de l'utilisateur cx = PgSQL.connect(database=config.DB_NAME,user=config.DB_USER,password=config.DB_PASSWD) cursor=cx.cursor() cursor.execute("""select droits from users where login='%s'""" % cred_user) rs = cursor.fetchone() cursor.close() droits = [] # on rassemble toutes les fonctions auxquelles on a droit for groupe in eval(rs[0]): droits.extend(self.groupes[groupe][1]) try: # on regarde si on a le droit d'executer la fonction if functionPath not in droits: # fonction interdite print "\nutilisation de la fonction %s interdite pour %s (%s)" % (functionPath,cred_user,request.getHost()[1]) errpage = error.ErrorPage(http.UNAUTHORIZED,"Unauthorized", "erreur, ressource %s non autorisée !" % (request.uri)) return errpage.render(request) except: print "\n pas d'autorisations pour " + cred_user + " !" errpage = error.ErrorPage(http.UNAUTHORIZED,"Unauthorized","erreur, ressource %s non autorisée !" % (request.uri)) return errpage.render(request) # fonction autorisée try: function = self._getFunction(functionPath) except xmlrpc.NoSuchFunction: self._cbRender( xmlrpclib.Fault(self.NOT_FOUND, "no such function %s" % functionPath), request ) else: request.setHeader("content-type", "text/xml") defer.maybeDeferred(function, cred_user, *args).addErrback( self._ebRender ).addCallback( self._cbRender, request ) return server.NOT_DONE_YET