# -*- 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
#
# page.py
#
# Surclasse de l'objet Page woven pour gestion de l'authentification,
# des sessions et de la connexion au back-end
#
###########################################################################
import ldap
from twisted.web import http, static, resource
from twisted.python import components
from twisted.web import server
from twisted.web.resource import Resource
from twisted.web.resource import ErrorPage
from zope.interface import Interface
from pyeole.encode import normalize
from zephir.web import config

# définition des classes servant a créer de nouvelles variables dans les sessions
class IPreferences(Interface):
    pass

class Preferences(components.Adapter):
    __implements__ = IPreferences

    def __init__(self,original):
        self.authentified = 'no'
        self.bad_auth = '0'
        self.password = ""
        self.user = ""
        components.Adapter.__init__(self,original)

components.registerAdapter(Preferences, server.Session, IPreferences)

def proxy(request):
    prefs = request.getSession(IPreferences)
    # récupération du proxy vers le backend zephir
    return getattr(prefs, 'zephir')

def get_user(request):
    prefs = request.getSession(IPreferences)
    # récupération de l'utilisateur authentifié
    return getattr(prefs, 'user').decode()

def get_password(request):
    prefs = request.getSession(IPreferences)
    # récupération de l'utilisateur authentifié
    ret = getattr(prefs, 'password')
    if isinstance(ret, bytes):
        ret = ret.decode()
    return ret

##################################################################
## Fonctions utilisées pour l'authentification des ressources web

def close_session(request):
    request.setHeader('WWW-authenticate', 'Basic realm="%s" domain="/"' % ("zephir",))

def require_auth(request):
    prefs = request.getSession(IPreferences)
    request.setResponseCode(http.UNAUTHORIZED)
    request.setHeader('WWW-authenticate', 'Basic realm="%s" domain="/"' % ("zephir",))
    errpage = ErrorPage(http.UNAUTHORIZED,"Unauthorized","401 Authentication required")
    bad_auth = getattr(prefs, 'bad_auth')
    # on incrémente bad_auth pour savoir combien de demandes d'autorisation on a fait
    setattr(prefs,'bad_auth',str(int(bad_auth)+1))
    return errpage

def check_auth(request):
    """vérifie si la requête est authentifiée
    """
    request.setHeader('WWW-authenticate', 'Basic realm="%s domain=/"' % ("zephir",))
    # request.setHeader('WWW-authenticate', 'Basic realm="%s" domain="/"' % ("zephir-parc",))
    # test de l'authentification
    prefs = request.getSession(IPreferences)
    # on regarde si la session est authentifiée
    auth_ok = getattr(prefs, 'authentified')
    cred_user = getattr(prefs, 'user')
    cred_password = getattr(prefs, 'password')
    # bad_auth : permet de savoir quand une tentative de login a echoué
    bad_auth = getattr(prefs, 'bad_auth')
    # retries : permet de forcer le nombre de demandes d'autorisations a
    # envoyer pour que le navigateur demande un nouveau login
    # (ex : 2 pour Galeon, 1 pour IE, ...)
    retries = 1
    if request.path == '/logout':
        return True
    if 'AppleWebKit' in request.getHeader('User-Agent') and not ('Chrome') in request.getHeader('User-Agent'):
        retries = 2
    if auth_ok == 'no' or cred_user == '':
        # une fois le nombre voulu de demandes envoyées, on récupère les nouveaus login/password
        if int(bad_auth) >= retries:
            cred_user = request.getUser()
            cred_password = normalize(request.getPassword())
            # on vérifie l'authentification dans l'annuaire LDAP
            if cred_password == "" or cred_user == "":
                return False
            try:
                query = ldap.initialize("ldap://{}".format(config.ADRESSE_LDAP))
                if config.LDAP_TLS == "oui":
                    query.start_tls_s()
                # on récupère le dn complet de l'utilisateur (l'uid et le dn doivent être accessibles en lecture)
                result=query.search_s(config.BASE_LDAP, ldap.SCOPE_SUBTREE, "(uid="+cred_user.decode()+")")
                cred_dn = result[0][0]
                # on tente une connexion à l'annuaire avec les informations données
                query.simple_bind_s(cred_dn,cred_password)
                query.unbind()
            except:
                # erreur d'authentification
                # on retourne un message d'erreur
                print("authentification incorrecte : ",request.host.host)
            else:
                # on enregistre la session
                # on lance la procédure de vérification de l'expiration de la session
                request.getSession().notifyOnExpire(lambda :close_session(request))
                # création du serveur xmlrpc
                # on modifie les préférences de la session pour indiquer qu'elle est authentifiée
                setattr(prefs,'authentified','yes')
                setattr(prefs,'bad_auth','0')
                setattr(prefs,'user',cred_user)
                setattr(prefs,'password',cred_password)
                setattr(prefs,'zephir', config.build_proxy(cred_user, cred_password))
                return True
        return False
    else:
        # request.getSession()
        # l'authentification a réussi, on retourne le rendu de la page
        return True


class FileEole(static.File):
    """surclasse de la classe static.File pour ajouter l'authentification et interdire de browser le répertoire
    """

    def directoryListing(self):
        return resource.NoResource("Oups ...")

    def getChild(self, path, request):
        if check_auth(request):
            return static.File.getChild(self, path, request)
        else:
            return require_auth(request)

# ressource sécurisée
class PageEole(Resource):

    def __init__(self, *args, **kwargs):
        self.client_pass = None
        self.zephir=None
        self.serveur_ldap = config.ADRESSE_LDAP
        # config.SERVEUR_LDAP
        Resource.__init__(self, *args, **kwargs)

    def render(self, request):
        if check_auth(request):
            ret = self.render_design(request)
            return ret.encode()
        else:
            return require_auth(request).render(request)
