# -*- coding: utf-8 -*-
#######################################################################################
#   Plinn - http://plinn.org                                                          #
#   © 2005-2013  Benoît PIN <pin@cri.ensmp.fr>                                        #
#                                                                                     #
#   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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.   #
#######################################################################################
""" Plinn registration tool: implements 3 modes to register members:
    anonymous, manager, reviewed.



"""

from Globals import InitializeClass, PersistentMapping
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.CMFDefault.RegistrationTool import RegistrationTool as BaseRegistrationTool
from AccessControl import ClassSecurityInfo, ModuleSecurityInfo
from AccessControl.Permission import Permission
from BTrees.OOBTree import OOBTree
from Products.CMFCore.permissions import ManagePortal, AddPortalMember
from Products.CMFCore.exceptions import AccessControl_Unauthorized
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import getUtilityByInterfaceName
from Products.GroupUserFolder.GroupsToolPermissions import ManageGroups
from Products.Plinn.utils import Message as _
from Products.Plinn.utils import translate
from Products.Plinn.utils import encodeQuopriEmail
from Products.Plinn.utils import encodeMailHeader
from DateTime import DateTime
from types import TupleType, ListType
from uuid import uuid4

security = ModuleSecurityInfo('Products.Plinn.RegistrationTool')
MODE_ANONYMOUS = 'anonymous'
security.declarePublic('MODE_ANONYMOUS')

MODE_MANAGER = 'manager'
security.declarePublic('MODE_MANAGER')

MODE_REVIEWED = 'reviewed'
security.declarePublic('MODE_REVIEWED')

MODES = [MODE_ANONYMOUS, MODE_MANAGER, MODE_REVIEWED]
security.declarePublic('MODES')

DEFAULT_MEMBER_GROUP = 'members'
security.declarePublic('DEFAULT_MEMBER_GROUP')



class RegistrationTool(BaseRegistrationTool) :

    """ Create and modify users by making calls to portal_membership.
    """
    
    meta_type = "Plinn Registration Tool"
    
    manage_options = ({'label' : 'Registration mode', 'action' : 'manage_regmode'}, ) + \
                        BaseRegistrationTool.manage_options
    
    security = ClassSecurityInfo()
    
    security.declareProtected( ManagePortal, 'manage_regmode' )
    manage_regmode = PageTemplateFile('www/configureRegistrationTool', globals(),
                                        __name__='manage_regmode')

    def __init__(self) :
        self._mode = MODE_ANONYMOUS
        self._chain = ''
        self._passwordResetRequests = OOBTree()
    
    security.declareProtected(ManagePortal, 'configureTool')
    def configureTool(self, registration_mode, chain, REQUEST=None) :
        """ """
        
        if registration_mode not in MODES :
            raise ValueError, "Unknown mode: " + registration_mode
        else :
            self._mode = registration_mode
            self._updatePortalRoleMappingForMode(registration_mode)
        
        wtool = getToolByName(self, 'portal_workflow')

        if registration_mode == MODE_REVIEWED :
            if not hasattr(wtool, '_chains_by_type') :
                wtool._chains_by_type = PersistentMapping()
            wfids = []
            chain = chain.strip()
            
            if chain == '(Default)' :
                try : del wtool._chains_by_type['Member Data']
                except KeyError : pass
                self._chain = chain
            else :
                for wfid in chain.replace(',', ' ').split(' ') :
                    if wfid :
                        if not wtool.getWorkflowById(wfid) :
                            raise ValueError, '"%s" is not a workflow ID.' % wfid
                        wfids.append(wfid)
    
                wtool._chains_by_type['Member Data'] = tuple(wfids)
                self._chain = ', '.join(wfids)
        else :
            wtool._chains_by_type['Member Data'] = tuple()
        
        if REQUEST :
            REQUEST.RESPONSE.redirect(self.absolute_url() + '/manage_regmode?manage_tabs_message=Saved changes.')

    def _updatePortalRoleMappingForMode(self, mode) :
    
        urlTool = getToolByName(self, 'portal_url')
        portal = urlTool.getPortalObject()
    
        if mode in [MODE_ANONYMOUS, MODE_REVIEWED] :
            portal.manage_permission(AddPortalMember, roles = ['Anonymous', 'Manager'], acquire=1)
        elif mode == MODE_MANAGER :
            portal.manage_permission(AddPortalMember, roles = ['Manager', 'UserManager'], acquire=0)
    
    security.declarePublic('getMode')
    def getMode(self) :
        # """ return current mode """
        return self._mode[:]
    
    security.declarePublic('getWfId')
    def getWfChain(self) :
        # """ return current workflow id """
        return self._chain
    
    security.declarePublic('roleMappingMismatch')
    def roleMappingMismatch(self) :
        # """ test if the role mapping is correct for the currrent mode """
        
        mode = self._mode
        urlTool = getToolByName(self, 'portal_url')
        portal = urlTool.getPortalObject()
                
        def rolesOfAddPortalMemberPerm() :
            p=Permission(AddPortalMember, [], portal)
            return p.getRoles()
        
        if mode in [MODE_ANONYMOUS, MODE_REVIEWED] :
            if 'Anonymous' in rolesOfAddPortalMemberPerm() : return False
            
        elif mode == MODE_MANAGER :
            roles = rolesOfAddPortalMemberPerm()
            if 'Manager' in roles or 'UserManager' in roles and len(roles) == 1 and type(roles) == TupleType :
                return False
        
        return True

    security.declareProtected(AddPortalMember, 'addMember')
    def addMember(self, id, password, roles=(), groups=(DEFAULT_MEMBER_GROUP,), domains='', properties=None) :
        """ Idem CMFCore but without default role """
        BaseRegistrationTool.addMember(self, id, password, roles=roles,
                                       domains=domains, properties=properties)

        if self.getMode() in [MODE_ANONYMOUS, MODE_MANAGER] :
            gtool = getToolByName(self, 'portal_groups')
            mtool = getToolByName(self, 'portal_membership')
            utool = getToolByName(self, 'portal_url')
            portal = utool.getPortalObject()
            isGrpManager = mtool.checkPermission(ManageGroups, portal) ## TODO : CMF2.1 compat
            aclu = self.aq_inner.acl_users

            for gid in groups:
                g = gtool.getGroupById(gid)
                if not isGrpManager :               
                    if gid != DEFAULT_MEMBER_GROUP:
                        raise AccessControl_Unauthorized, 'You are not allowed to join arbitrary group.'

                if g is None :
                    gtool.addGroup(gid)
                aclu.changeUser(aclu.getGroupPrefix() +gid, roles=['Member', ])
                g = gtool.getGroupById(gid)
                g.addMember(id)


    def afterAdd(self, member, id, password, properties):
        """ notify member creation """
        member.notifyWorkflowCreated()
        member.indexObject()
    

    security.declarePublic('requestPasswordReset')
    def requestPasswordReset(self, userid):
        """ add uuid / (userid, expiration) pair and return uuid """
        self.clearExpiredPasswordResetRequests()
        mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
        member = mtool.getMemberById(userid)
        if member :
            uuid = str(uuid4())
            while self._passwordResetRequests.has_key(uuid) :
                uuid = str(uuid4())
            self._passwordResetRequests[uuid] = (userid, DateTime() + 1)
            utool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IURLTool')
            ptool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IPropertiesTool')
            # fuck : mailhost récupéré avec getUtilityByInterfaceName n'est pas correctement
            # wrappé. Un « unrestrictedTraverse » ne marche pas.
            # mailhost = getUtilityByInterfaceName('Products.MailHost.interfaces.IMailHost')
            portal = utool.getPortalObject()
            mailhost = portal.MailHost
            sender = encodeQuopriEmail(ptool.getProperty('email_from_name'), ptool.getProperty('email_from_address'))
            to = encodeQuopriEmail(member.getMemberFullName(nameBefore=0), member.getProperty('email'))
            subject = translate(_('How to reset your password on the %s website')) % ptool.getProperty('title')
            subject = encodeMailHeader(subject)
            options = {'fullName' : member.getMemberFullName(nameBefore=0),
                       'siteName' : ptool.getProperty('title'),
                       'resetPasswordUrl' : '%s/password_reset_form/%s' % (utool(), uuid)}
            body = self.password_reset_mail(options)
            message = self.echange_mail_template(From=sender,
                                                 To=to,
                                                 Subject=subject,
                                                 ContentType = 'text/plain',
                                                 charset = 'UTF-8',
                                                 body=body)
            mailhost.send(message)
            return
        
        return _('Unknown user name. Please retry.')
    
    security.declarePrivate('clearExpiredPasswordResetRequests')
    def clearExpiredPasswordResetRequests(self):
        now = DateTime()
        for uuid, record in self._passwordResetRequests.items() :
            userid, date = record
            if date < now :
                del self._passwordResetRequests[uuid]
    
    
    security.declarePublic('resetPassword')
    def resetPassword(self, uuid, password, confirm) :
        record = self._passwordResetRequests.get(uuid)
        if not record :
            return None, _('Invalid reset password request.')
        
        userid, expiration = record
        now = DateTime()
        if expiration < now :
            self.clearExpiredPasswordResetRequests()
            return None, _('Your reset password request has expired. You can ask a new one.')
        
        msg = self.testPasswordValidity(password, confirm=confirm)
        if not msg : # None if everything ok. Err message otherwise.
            mtool = getUtilityByInterfaceName('Products.CMFCore.interfaces.IMembershipTool')
            member = mtool.getMemberById(userid)
            if member :
                member.setSecurityProfile(password=password)
                del self._passwordResetRequests[uuid]
                return  userid, _('Password successfully reset.')
            else :
                return None, _('"%s" username not found.') % userid
            
        
InitializeClass(RegistrationTool)