#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


__doc__ = """Glasnost Password Accounts Server"""

__version__ = '$Revision$'[11:-2]


import copy
import sha
import sys

glasnostPythonDir = '/usr/local/lib/glasnost-devel' # changed on make install
sys.path.insert(0, glasnostPythonDir)

import glasnost

import glasnost.common.context as context
import glasnost.common.faults as faults
import glasnost.common.PasswordAccountsCommon as commonPasswordAccounts
from glasnost.common.tools import iso8859_15, sendMail
import glasnost.common.tools_new as commonTools

import glasnost.server.ObjectsServer as objects

from glasnost.proxy.GroupsProxy import getSetContainedIds
from glasnost.proxy.tools import getObject, getProxy, getProxyForServerRole


applicationName = 'PasswordAccountsServer'
applicationRole = 'passwordaccounts'
dispatcher = None


# FIXME: those classes are necessary to upgrade data from
# AuthenticationLoginPasswordServer.pickle; they should be removed as
# soon as it is no longer necessary to access this file.
class AccountLoginPassword: pass
class AdminAuthenticationLoginPassword: pass
class AuthenticationLoginPasswordVirtualServer: pass


class AdminPasswordAccounts(objects.AdminServerMixin,
                            commonPasswordAccounts.AdminPasswordAccounts):
    def checkModifyIsPossible(self, changes, givenSlotNames = None):
        # This change is irreversible.
        if self.storePasswordsInClearText == 0 \
               and changes.storePasswordsInClearText == 1:
            raise faults.UnableToChangePasswordStorage()
        objects.AdminServerMixin.checkModifyIsPossible(
                self, changes, givenSlotNames = givenSlotNames)

    def modify(self, changes, givenSlotNames = None):
        inClearText = self.storePasswordsInClearText
        objects.AdminServerMixin.modify(self, changes,
                givenSlotNames = givenSlotNames)
        if not inClearText and not self.storePasswordsInClearText:
            # we now have to hash every passwords
            server = self.getServer()
            virtualServerId = context.getVar('applicationId')
            virtualServer = server.getVirtualServer(virtualServerId)
            for object in virtualServer.objects.values():
                object.password = sha.new(object.password).hexdigest()
            virtualServer.markAllAsDirtyFIXME()
objects.register(AdminPasswordAccounts)


class PasswordAccount(objects.ObjectServerMixin,
                      commonPasswordAccounts.PasswordAccount):
    def checkModifyIsPossible(self, changes, givenSlotNames = None):
        objects.ObjectServerMixin.checkModifyIsPossible(
                self, changes, givenSlotNames = givenSlotNames)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        if (not givenSlotNames or 'login' in givenSlotNames) \
               and changes.login != self.login and changes.login is not None:
            if virtualServer.objectsByLogin.has_key(changes.login) \
                    and changes.id != virtualServer.objectsByLogin[
                            changes.login].id:
                raise faults.DuplicateLogin(changes.login)

    def clear(self):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        if virtualServer.objectsByLogin.has_key(self.login):
            del virtualServer.objectsByLogin[self.login]

    def modify(self, changes, givenSlotNames = None):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getServer().getVirtualServer(virtualServerId)
        login = self.login
        if not virtualServer.admin.userCanChoosePassword:
            self.password_kind = copy.copy(self.password_kind)
            self.password_kind.hasToModify = 0
        objects.ObjectServerMixin.modify(
                self, changes, givenSlotNames = givenSlotNames)
        if not virtualServer.admin.userCanChoosePassword:
            del self.password_kind
        if self.login != login:
            if login is not None:
                del virtualServer.objectsByLogin[login]
            if self.login is not None:
                virtualServer.objectsByLogin[self.login] = self
        if not virtualServer.admin.storePasswordsInClearText:
            self.password = sha.new(self.password).hexdigest()
objects.register(PasswordAccount)


class PasswordAccountsVirtualServer(objects.ObjectsVirtualServer):
    objectsByLogin = None

    def init(self):
        objects.ObjectsVirtualServer.init(self)
        self.objectsByLogin = {}


class PasswordAccountsServer(
        commonPasswordAccounts.PasswordAccountsCommonMixin,
        objects.ObjectsServer):
    VirtualServer = PasswordAccountsVirtualServer

    def addObjectXmlRpc(self, objectImport):
        objectId = objects.ObjectsServer.addObjectXmlRpc(self, objectImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        object = virtualServer.loadObjectCore(objectId)
        if virtualServer.objectsByLogin.has_key(object.login):
            # Login already used.
            del virtualServer.objects[objectId]
            virtualServer.markObjectAsDeleted(objectId)
            virtualServer.markCoreAsDirty()
            raise faults.DuplicateLogin(object.login)
        virtualServer.objectsByLogin[object.login] = object

        if not virtualServer.admin.userCanChoosePassword or \
                not object.password:
            object.password = commonTools.makepassword()
            self.emailPasswordToUser(object, notYetHashed = 1)
        
        if not virtualServer.admin.storePasswordsInClearText:
            object.password = sha.new(object.password).hexdigest()

        virtualServer.markCoreAsDirty()
        return objectId

    def canChangePassword(self):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        return virtualServer.admin.userCanChoosePassword

    def changePassword(self, oldPassword, newPassword):
        userId = getProxyForServerRole('identities').getUserId()
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)

        if not virtualServer.admin.storePasswordsInClearText:
            cmpPassword = sha.new(oldPassword).hexdigest()
        else:
            cmpPassword = oldPassword
        
        for object in virtualServer.objects.values():
            if object.identityId == userId:
                if object.password != cmpPassword:
                    raise faults.WrongPassword(oldPassword)
                object.password = newPassword
                if not virtualServer.admin.storePasswordsInClearText:
                    object.password = sha.new(object.password).hexdigest()
                virtualServer.markObjectAsDirty(object)
                return

        # TODO: hum, should be faults.WrongIdentity
        raise faults.WrongLogin('')


    def checkObjectAuthenticationXmlRpc(self, loginImport, passwordImport):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        login = iso8859_15(loginImport)
        password = iso8859_15(passwordImport)
        if not virtualServer.objectsByLogin.has_key(login):
            raise faults.WrongLogin(login)
        object = virtualServer.objectsByLogin[login]
        if not virtualServer.admin.storePasswordsInClearText:
            cmpPassword = sha.new(password).hexdigest()
        else:
            cmpPassword = password
        if object.password and cmpPassword != object.password:
            raise faults.WrongPassword(password)
        identitiesProxy = getProxy(object.identityId)
        return [identitiesProxy.getUserToken(object.identityId), 'password']

    def emailPassword(self, loginImport):
        login = iso8859_15(loginImport)
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        for object in virtualServer.objects.values():
            if object.login == login:
                self.emailPasswordToUser(object)

    def emailPasswordToUser(self, object, notYetHashed = 0):
        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)

        if not notYetHashed and \
                not virtualServer.admin.storePasswordsInClearText:
            # TODO: mail user explaining we can generate a new password for
            # him and provide him with an url with a token (so that others
            # can't force password changes)
            print 'Not sending because passwords are not in plain text'
            return

        identitiesProxy = getProxy(object.identityId)
        #identity = identitiesProxy.getObject(object.identityId)
        #if not identity.personId:
        #    print 'no personId for this identity'
        #    return

        emailAddress = identitiesProxy.getObjectEmail(object.identityId)
        if not emailAddress:
            raise faults.BadEmailAddress('')

        personName, personLanguage = identitiesProxy.getObjectLabelAndLanguage(
                object.identityId)

        toAddress = [emailAddress]
        password = object.password

        messageFileName = commonTools.getConfig(
                commonTools.extractDispatcherId(object.id),
                'WelcomeEmail-%s' % personLanguage)
        if not messageFileName:
            messageFileName = commonTools.getConfig(
                    commonTools.extractDispatcherId(object.id), 'WelcomeEmail')
            
        messageSubject = commonTools.getConfig(
                commonTools.extractDispatcherId(object.id),
                'WelcomeEmailSubject',
                default = '[Glasnost] login & password')
        
        message = """\
Here are the Glasnost login & password you have requested.

Web Site:   %(hostName)s
User:       %(user)s
Login:      %(login)s
Password:   %(password)s

The Glasnost administrator - %(fromAddress)s
"""

        if messageFileName:
            message = open(messageFileName).read()

        virtualServerId = context.getVar('applicationId')
        hostName = getProxyForServerRole('virtualhosts').getHostName(
                virtualServerId)
        fromAddresses = self.getAdminEmailAddresses(stopAsap = 1)
        message = message % {
                'user': personName,
                'fromAddress': fromAddresses[0],
                'hostName': hostName,
                'login': object.login,
                'password': object.password,
                }

        sendMail(
                mailFrom = fromAddresses[0],
                mailTo = toAddress,
                mailSubject = messageSubject,
                mailMessage = message,
                moreHeaders = {
                    'Content-Type': 'text/plain; charset=iso-8859-1',
                    'Content-Transfer-Encoding': '8bit',
                    }
                )




    def fillEmptyVirtualServer(self, virtualServer):
        objects.ObjectsServer.fillEmptyVirtualServer(self, virtualServer)

        # Upgrade to version 0001_0028.
        import cPickle
        import os
        authenticationPickleFilePath = os.path.join(
                virtualServer.dataDirectoryPath,
                'AuthenticationLoginPasswordServer.pickle')
        if os.access(authenticationPickleFilePath, os.F_OK):
            print 'Importing AuthenticationLoginPasswordServer data for %s.' \
                  % virtualServer.virtualServerId
            authenticationRcFile = open(authenticationPickleFilePath, 'rb')
            authenticationVersion = self.readFileVersion(authenticationRcFile)
            authenticationVirtualServer = cPickle.load(authenticationRcFile)
            authenticationRcFile.close()

            admin = virtualServer.admin
            authenticationAdmin = authenticationVirtualServer.admin
            if hasattr(authenticationAdmin, 'adminsSet'):
                admin.adminsSet = authenticationAdmin.adminsSet
            if hasattr(authenticationAdmin, 'writersSet'):
                admin.writersSet = authenticationAdmin.writersSet
            if hasattr(authenticationAdmin, 'readersSet'):
                admin.readersSet = authenticationAdmin.readersSet
            if hasattr(authenticationAdmin, 'stockPasswordsInClearText'):
                admin.storePasswordsInClearText \
                        = authenticationAdmin.stockPasswordsInClearText
            if hasattr(authenticationAdmin, 'userCanChoosePassword'):
                admin.userCanChoosePassword \
                        = authenticationAdmin.userCanChoosePassword
            virtualServer.markAdminAsDirty(virtualServer.admin)

            virtualServer.nextLocalId = 1
            virtualServer.markCoreAsDirty()

            personIds = authenticationVirtualServer.authentications.keys()
            personIds.sort(
                lambda x,y: cmp(
                    int(commonTools.extractLocalId(x)),
                    int(commonTools.extractLocalId(y)) ))
            
            if personIds:
                virtualServer.nextLocalId = int(commonTools.extractLocalId(
                        personIds[-1])) + 1

            for personId in personIds:
                authentication = authenticationVirtualServer.authentications[
                        personId]
                passwordAccount = PasswordAccount()
                passwordAccount.login = authentication.login
                passwordAccount.password = authentication.password
                passwordAccount.id =  '%s/%s' % (
                        commonTools.makeApplicationId(personId,
                                'passwordaccounts'),
                        commonTools.extractLocalId(personId))
                passwordAccount.identityId = '%s/%s' % (
                        commonTools.makeApplicationId(personId, 'identities'),
                        commonTools.extractLocalId(personId))
                virtualServer.objects[passwordAccount.id] = passwordAccount
                virtualServer.objectsByLogin[
                        passwordAccount.login] = passwordAccount
                passwordAccount.saveNonCore()
                passwordAccount.releaseNonCore()
                virtualServer.markObjectAsDirty(passwordAccount)
                virtualServer.markCoreAsDirty()


    def getAdminEmailAddresses(self, stopAsap = 0):
        # if an address is set in the config file we use it; otherwise we
        # must grab addresses from self.admin.adminsSet
        email = commonTools.getConfig(
            commonTools.extractDispatcherId(context.getVar('dispatcherId')),
            'AdminEmailAddress')
        if email:
            return [email]

        virtualServerId = context.getVar('applicationId')
        virtualServer = self.getVirtualServer(virtualServerId)
        try:
            adminIds = getSetContainedIds(
                    virtualServer.admin.adminsSet, ['identities'],
                    raiseWhenUncountable = 1)
        except faults.UncountableGroup:
            adminIds = []
        
        identitiesProxy = getProxyForServerRole('identities')
        toAddresses = []
        for adminId in adminIds:
            emailAddress = identitiesProxy.getObjectEmail(adminId)
            if emailAddress:
                toAddresses.append(emailAddress)
                if stopAsap:
                    return toAddresses

        if not toAddresses:
            return [virtualServer.adminEmailAddress]

        return toAddresses


    def registerPublicMethods(self):
        objects.ObjectsServer.registerPublicMethods(self)
        self.registerPublicMethod('canChangePassword')
        self.registerPublicMethod('changePassword')
        self.registerPublicMethod('checkObjectAuthentication',
                self.checkObjectAuthenticationXmlRpc)
        self.registerPublicMethod('emailPassword')


passwordAccountsServer = PasswordAccountsServer()


if __name__ == "__main__":
    passwordAccountsServer.launch(applicationName, applicationRole)

