Le backend xml-rpc

  1. Les codes d'erreurs
  2. Le transport xml-rpc
  3. définir des utilisateurs authentifiés pour le serveur XML-RPC de twisted
  4. Vérification de l'authentification des utilisateurs et interdiction de fonctions

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
Contrairement au status codes unix, bien remarquer qu'ici la convention est 0 pour un code d'erreur, et 1 pur un code retour normal. Ce choix est dû au fait qu'en python, 0 est aussi un booléen équivalent à False. Exception est ici un message du type 'libpq.OperationalError' ou 'libpq.IntegrityError', qui est ensuite convertit dans le frontend en DatabaseError. Si rien n'est renvoyé, le fronted génère une BackendError. msg est le message renvoyé par la base de donnée, de manière, côté frontend, à récupérer le nom de la table qui a provoqué l'erreur.

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

Index

mar mar 23 09:10:33 CET 2004 Version: 0.7