# -*- coding: UTF-8 -*-


# Expression
# By: Frederic Peters <fpeters@entrouvert.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#
# Copyright (C) 2004 Entr'ouvert, Frederic Peters & Emmanuel Raviart
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.


"""Sessions Module"""


import errno
import os
import socket

import libxml2

import dataholders
import directories
import elements
import environs
import faults
import filesystems
import locations
import logs
import modules
import namespaces
import stations
import things


class Session(things.Thing):
    _account = None

    def deleteAccountPath(self):
        nodes = self.evaluateXpath("yep:account")
        if nodes:
            node = nodes[0]
            node.unlinkNode()
            node.freeNode()
            self.getDataHolder().isDirty = True

    def deleteLassoSessionDump(self):
        nodes = self.evaluateXpath("yep:lassoSessionDump")
        if nodes:
            node = nodes[0]
            node.unlinkNode()
            node.freeNode()
            self.getDataHolder().isDirty = True

    def deletePublishToken(self):
        nodes = self.evaluateXpath("yep:publishToken")
        if nodes:
            node = nodes[0]
            node.unlinkNode()
            node.freeNode()
            self.getDataHolder().isDirty = True

    def getAccount(self, acceptOnlyAccount = True):
        if self._account is None:
            accountAbsolutePath = self.getAccountAbsolutePath()
            if accountAbsolutePath:
                try:
                    accountHolder = dataholders.DataHolder(
                        pathFragment = accountAbsolutePath, mimeType = "text/xml",
                        isRootElder = True,
                        containedFileSystem = filesystems.PartialFileSystem(accountAbsolutePath))
                except IOError, error:
                    if error.errno == errno.ENOENT:
                        logs.debug("""Account at path "%s" doesn't exist.""" % accountAbsolutePath)
                        self._account = "none"
                    else:
                        raise
                else:
                    account = accountHolder.getRootElement()
                    self._account = account
            else:
                accountNodes = self.evaluateXpath("yep:account")
                if accountNodes:
                    self._account = elements.newElement(
                        accountNodes[0], previous = self, owner = self)
                else:
                    self._account = "none"
        if self._account == "none":
            return None
        if acceptOnlyAccount and not self._account.isAccount:
            return None
        return self._account

    def getAccountAbsolutePath(self):
        nodes = self.evaluateXpath("yep:account/@src")
        if nodes:
            accountPath = nodes[0].content
            return self.convertPathToAbsolute(accountPath)
        else:
            return None

    def getAuthenticationMethod(self):
        account = self.getAccount()
        if account is None:
            # FIXME: If lassoSessionDump (which contains Liberty Alliance authentication
            # assertions) is not None, then use Lasso to get authentication method(s) used.
            lassoSessionDump = self.getLassoSessionDump()
            if lassoSessionDump is None:
                return None
            import lasso
            lassoSession = lasso.Session.newFromDump(lassoSessionDump)
            if lassoSession is None:
                logs.warning("Ignoring invalid Lasso session dump = %s" % lassoSessionDump)
                return None
            lassoAuthenticationMethod = lassoSession.getAuthenticationMethod()
            if lassoAuthenticationMethod == lasso.samlAuthenticationMethodPassword:
                authenticationMethod = "password"
            elif lassoAuthenticationMethod == lasso.samlAuthenticationMethodSoftwarePki:
                authenticationMethod = "certificate"
            elif lassoAuthenticationMethod == lasso.samlAuthenticationMethodUnspecified:
                authenticationMethod = "unspecified"
            else:
                logs.debug('Unknown authentication method = "%s"' % lassoAuthenticationMethod)
            return authenticationMethod
        return account.getAuthenticationMethod()

    def getClientHostName(self):
        nodes = self.evaluateXpath("yep:clientHostName")
        if nodes:
            return nodes[0].content
        else:
            return None

    def getClientIpAddress(self):
        nodes = self.evaluateXpath("yep:clientIpAddress")
        if nodes:
            return nodes[0].content
        else:
            return None

    def getClientPort(self):
        nodes = self.evaluateXpath("yep:clientPort")
        if nodes:
            return int(nodes[0].content)
        else:
            return None

    def getLassoSessionDump(self):
        nodes = self.evaluateXpath("yep:lassoSessionDump")
        if nodes:
            return nodes[0].content
        else:
            return None

    def getPublishToken(self):
        nodes = self.evaluateXpath("yep:publishToken")
        if nodes:
            return nodes[0].content not in ("0", "false")
        else:
            return False

    def getToken(self):
        dataHolder = self.getDataHolder()
        localId = dataHolder.localId
        if dataHolder.isRootElder and "/" in localId:
            localId = localId.split("/")[-1]
        if localId.endswith(".xml"):
            sessionToken = localId[:-len(".xml")]
        else:
            sessionToken = localId
        return sessionToken

    def getUser(self):
        account = self.getAccount(acceptOnlyAccount = False)
        if account is None:
            return None
        if account.isAccount:
            return account.getUser()
        else:
            return account

    def setAccountAbsolutePath(self, accountAbsolutePath):
        accountBaseRelativePath = self.convertAbsolutePathToBaseRelative(accountAbsolutePath)
        self.setElementAttribute("yep:account", "src", accountBaseRelativePath)

    def setClientHostName(self, clientHostName):
        self.setElementContent("yep:clientHostName", clientHostName)
        self.getDataHolder().isDirty = True

    def setClientIpAddress(self, clientIpAddress):
        self.setElementContent("yep:clientIpAddress", clientIpAddress)
        self.getDataHolder().isDirty = True

    def setClientPort(self, clientPort):
        self.setElementContent("yep:clientPort", str(clientPort))
        self.getDataHolder().isDirty = True

    def setEnviron(self):
        baseEnviron = environs.get(_level = "handleHttpCommand")
        baseEnviron.setVar("session", self)
        if environs.getVar("canUseCookie"):
            baseEnviron.setVar("testCookieSupport", True)

    def setLassoSessionDump(self, lassoSessionDump):
        self.setElementContent("yep:lassoSessionDump", lassoSessionDump)
        self.getDataHolder().isDirty = True

    def setPublishToken(self, publishToken):
        if publishToken:
            publishToken = "true"
        else:
            publishToken = "false"
        self.setElementContent("yep:publishToken", publishToken)
        self.getDataHolder().isDirty = True

    def validate(self):
        # FIXME: Remove old sessions.
        httpRequestHandler = environs.getVar("httpRequestHandler")
        clientIpAddress = httpRequestHandler.client_address[0]
        if clientIpAddress != self.clientIpAddress and self.clientIpAddress is not None:
            logs.info('Tentative use of session "%s" (owned by %s) by %s' % (
                self.token, self.clientIpAddress, clientIpAddress))
            return False
        # FIXME:  Change session expirationTime and lastTime.
        return True

    clientHostName = property(getClientHostName, setClientHostName)
    clientIpAddress = property(getClientIpAddress, setClientIpAddress)
    clientPort = property(getClientPort, setClientPort)
    publishToken = property(getPublishToken, setPublishToken, deletePublishToken)
    token = property(getToken)


class SessionHolder(dataholders.XmlHolder):
    isDirty = False

    def save(self):
        super(SessionHolder, self).save()
        if self.isDirty:
            del self.isDirty


class SessionsHolder(directories.DirectoryHolder):
    def getItem(self, sessionToken):
        sessionToken = sessionToken + '.xml'
        sessionHolder = super(SessionsHolder, self).getItem(sessionToken)
        if sessionHolder is None:
            return None
        session = sessionHolder.getRootElement()
        if not session.validate():
            return None
        return sessionHolder


def createSession():
    """Create a new session and set it as the current session.

    Note: Use function getOrCreateSession() if you don't want to create a new session when one
    already exists.
    """
    rootDataHolder = environs.getVar("rootStation")
    sessionsPath = rootDataHolder.getConfigAbsolutePath("yep:sessionsPath")
    sessionsDirectoryHolder = SessionsHolder(
        pathFragment = sessionsPath, previous = rootDataHolder,
        mimeType = "text/xml", isRootElder = True,
        containedFileSystem = filesystems.PartialFileSystem(sessionsPath))
    sessionFeature = modules.getElementFeature(namespaces.yep.uri, "session")
    sessionHolder = sessionFeature.newXmlHolder(sessionsDirectoryHolder)
    session = sessionHolder.getRootElement()
    session.setEnviron()
    return session


def getOrCreateSession():
    """Return the current session (create it if it doesn't exist yet).

    Note: Use environs.getVar("session") instead of getOrCreateSession() if you
    don't want to create a session when none exists.
    """
    session = environs.getVar("session")
    if session is None:
        session = createSession()
    return session


def newSessionHolder(directoryHolder, pathFragment = None, uriPathFragment = None,
                     temporary = False):
    sessionHolder = dataholders.newXmlHolder(
        namespaces.yep.uri, "session", directoryHolder, pathFragment = pathFragment,
        uriPathFragment = uriPathFragment, temporary = temporary)
    session = sessionHolder.getRootElement()
    httpRequestHandler = environs.getVar("httpRequestHandler")
    clientIpAddress = httpRequestHandler.client_address[0]
    session.clientIpAddress = clientIpAddress
    session.clientPort = httpRequestHandler.client_address[1]
    clientHostName = socket.getfqdn(clientIpAddress)
    if clientHostName != clientIpAddress:
        session.clientHostName = clientHostName
    return sessionHolder


def retrieveSession(sessionToken):
    rootDataHolder = environs.getVar('rootStation')
    try:
        sessionsPath = rootDataHolder.getConfigAbsolutePath("yep:sessionsPath")
    except KeyError:
        return None
    sessionsDirectoryHolder = SessionsHolder(
        pathFragment = sessionsPath, previous = rootDataHolder,
        mimeType = "text/xml", isRootElder = True,
        containedFileSystem = filesystems.PartialFileSystem(sessionsPath))
    sessionHolder = sessionsDirectoryHolder.getItem(sessionToken)
    if sessionHolder is None:
        return None
    return sessionHolder.getRootElement()


elements.registerElement(
    namespaces.yep.uri, "session", Session,
    "http://www.entrouvert.org/expression/schemas/User.xsd",
    "http://www.entrouvert.org/expression/descriptions/Session.xml",
    SessionHolder, newSessionHolder)

# SessionsHolder.register()
