# -*- coding: UTF-8 -*-
###########################################################################
# Eole NG - 2007
# Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon)
# Licence CeCill  cf /root/LicenceEole.txt
# eole@ac-dijon.fr
#
# xmlrpceole.py
#
# Version modifiée du serveur XMLRPC twisted pour gérer
# l'authentification et la gestion des droits
#
###########################################################################
import twisted
from twisted.web import http
from twisted.web.resource import ErrorPage
from zephir.backend import config
from zephir.backend.lib_backend import ResourceAuthError

from twisted.internet import defer
from twisted.web import xmlrpc, server
from twisted.web.xmlrpc import Fault
from zephir.eolerpclib import xmlrpclib
import psycopg2 as PgSQL
import time
import copy

try:
    import ldap
except:
    pass

passwd_args = ('pass', 'pwd')

def convert(objet):
    """Transforme les objets unicode contenus dans un objet en chaines
    """
    if type(objet) == tuple:
        l = []
        for item in objet:
            l.append(convert(item))
        return l
    if type(objet) == list:
        l = []
        for item in objet:
            l.append(convert(item))
        return l
    if type(objet) == dict:
        dico={}
        for cle in list(objet.keys()):
            dico[cle] = convert(objet[cle])
        return dico
    if type(objet) == bytes:
        string =  objet.encode()
        return string
    return objet


# serveur sécurisé XMLPRC
class XMLRPCEole(xmlrpc.XMLRPC):


    def __init__(self):
        xmlrpc.XMLRPC.__init__(self)
        # définition des autorisations pour chaque utilisateur
        self.serveur_ldap = "ldap://{}".format(config.ADRESSE_LDAP)
        # reload_perms : indique que les permissions ont changé
        # et qu'il faut les recharger
        self.reload_perms = 0
        self.load_groupes()

    def load_groupes(self):
        # chargement des groupes de droits
        cx = PgSQL.connect(database='zephir',user='zephir',password=config.DB_PASSWD)
        db_cursor = cx.cursor()
        db_cursor.execute("""select id,libelle,droits from groupes_droits""")
        res = db_cursor.fetchall()
        db_cursor.close()
        cx.close()
        self.groupes={}
        for groupe in res:
            self.groupes[int(groupe[0])]=[str(res[1]),eval(str(groupe[2]))]

    def render_POST(self,request):
        """examine la requête transmise par le client et apelle la procédure
        correspondante si ses autorisations sont suffisantes"""
        # test de l'authentification
        cred_user = request.getUser().decode()
        cred_password = request.getPassword().decode()
        # dans le cas de l'utilisateur zephir, on ne fait pas de vérification sur ldap
        if cred_user != 'zephir' or cred_password != 'zephir':
            # on tente une connexion au serveur ldap avec ce login et mot de passe
            retry_auth = 0
            while retry_auth < 2:
                try:
                    if cred_password == "":
                        cred_password = None
                    query = ldap.initialize(self.serveur_ldap)
                    if config.LDAP_TLS == "oui":
                        query.start_tls_s()
                    # on récupère le dn complet de l'utilisateur
                    result = query.search_s(config.BASE_LDAP, ldap.SCOPE_SUBTREE, "(uid="+cred_user+")")
                    cred_dn = result[0][0]
                    query.simple_bind_s(cred_dn,cred_password)
                    query.unbind()
                except ldap.SERVER_DOWN:
                    print("ldap server not responding, retrying authentification")
                    time.sleep(0.3)
                    retry_auth += 1
                except:
                    # erreur d'authentification
                    # on retourne un message d'erreur
                    print("\nauthentification incorrecte : ",request.host.host)
                    errpage = ErrorPage(http.UNAUTHORIZED,"Unauthorized","401 Authentication required")
                    return errpage.render(request)
                    retry_auth = 2
                else:
                    retry_auth = 2
                    pass
                    # print "\nclient authentifié : ",request.getHost()[1]

        request.content.seek(0, 0)
        request.setHeader("content-type", "text/xml")
        try:
            args, functionPath = xmlrpclib.loads(request.content.read(),
                use_datetime=self.useDateTime)
        except Exception as e:
            f = Fault(self.FAILURE, "Can't deserialize input: %s" % (e,))
            self._cbRender(f, request)
        else:
            # Récupération de l'ip de connexion du serveur
            ip_publique = None
            if functionPath == 'uucp.maj_site':
                ip_publique = request.getClientIP()
            args_list=[]
            if ip_publique is not None:
                args_list.append(ip_publique)
            for arg in args:
                arg_conv=convert(arg)
                args_list.append(arg_conv)
            args = tuple(args_list)

            # 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()
            # l'authentification est insensible à la casse (#32038)
            cursor.execute("""select droits from users where LOWER(login) = LOWER(%s)""", (cred_user,))
            rs = cursor.fetchone()
            cursor.close()
            cx.close()
            droits = []
            # on rassemble toutes les fonctions auxquelles on a droit
            if rs == [] or rs is None:
                groupe = []
            else:
                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
                    if request.host.host not in ["localhost", "127.0.0.1"]:
                        host_addr = " (%s)" % request.host.host
                    else:
                        host_addr = ""
                    print("\nutilisation de la fonction %s interdite pour %s%s" % (functionPath,cred_user,host_addr))
                    errpage = 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 = ErrorPage(http.UNAUTHORIZED,"Unauthorized","erreur, ressource %s non autorisée !" % (request.uri))
                return errpage.render(request)
            # fonction autorisée
            try:
                function = self.lookupProcedure(functionPath)
            except Fault as f:
                self._cbRender(f, request)
            else:
                if config.LOG_ACTIONS and cred_user not in ('zephir','', None):
                    try:
                        # détection des arguments de fonctions de type 'mot de passe' (#8459)
                        noprint_args = []
                        for index, func_arg in enumerate(function.__code__.co_varnames):
                            for pass_arg in passwd_args:
                                if func_arg.startswith(pass_arg):
                                    # on repère l'indice de l'argument dans la signature de 'function'
                                    noprint_args.append(index - 2)
                        print_args = []
                        for idx_arg, arg in enumerate(args):
                            if idx_arg in noprint_args:
                                print_args.append("*****")
                            else:
                                print_args.append(arg)
                        print("ZEPHIR_BACKEND : %s -> %s" % (cred_user, functionPath), tuple(print_args))
                    except Exception as e:
                        print("Erreur lors du log d'une action : ", str(e))
                responseFailed = []
                request.notifyFinish().addErrback(responseFailed.append)
                if getattr(function, 'withRequest', False):
                    d = defer.maybeDeferred(function, request, cred_user, *args)
                else:
                    d = defer.maybeDeferred(function, cred_user, *args)
                d.addErrback(self.ebRender, request)
                d.addCallback(self._cbRender, request, responseFailed)
        return server.NOT_DONE_YET

    def ebRender(self, ex, request):
        """errback intermédiaire pour catcher les ressources non autorisées"""
        if ex.type == ResourceAuthError:
            msg_err = ex.getErrorMessage()
            errpage = ErrorPage(http.UNAUTHORIZED,"Unauthorized", "erreur, autorisations insuffisantes : {0} !".format(msg_err))
            config.log.msg("tentative d'accès à une ressource interdite pour {0} : {1}".format(request.getUser(), msg_err))
            return errpage.render(request)
        else:
            self._ebRender(ex)

