#! /usr/bin/env python

#############################################################
#                                                           #
#   Author: Bertrand Neron                                  #
#   Organization:'Biological Software and Databases' Group, #
#                Institut Pasteur, Paris.                   #
#   Distributed under GPLv2 Licence. Please refer to the    #
#   COPYING.LIB document.                                   #
#                                                           #
#############################################################

import sys
from hashlib import md5
import os.path

MOBYLEHOME = None
if os.environ.has_key('MOBYLEHOME'):
    MOBYLEHOME = os.environ['MOBYLEHOME']
if not MOBYLEHOME:
    sys.exit('MOBYLEHOME must be defined in your environment')

if ( os.path.join( MOBYLEHOME , 'Src' ) ) not in sys.path:    
    sys.path.append( os.path.join( MOBYLEHOME , 'Src' ) )


from Mobyle.ConfigManager import Config
from Mobyle.Session import Session , Transaction
from Mobyle.AuthenticatedSession import AuthenticatedSession
from Mobyle.MobyleError import MobyleError , SessionError 
from Mobyle.Net import EmailAddress , Email


    
class SessionPasswd( Session ):
    
    PASS_MIN_LEN = 6
    PASS_MAX_LEN = 12
    STRENGTH = [ 'blank', 'Very Weak', 'Weak', 'Medium', 'Strong', 'Very Strong']
    
    def __init__( self , user_email  , cfg ):
        self.newPasswd = None
        self.cfg = cfg
        self.user_email =  user_email     
        sessionDir , key = self._findSession()
        Session.__init__( self, sessionDir , key , self.cfg )
        if str(self.user_email) != self.getEmail():
            raise MobyleError , "the email %s in xml does not match the session Key %s" % ( self.getEmail() , key ) 
    
    def crypt(self , clearpasswd):
        newMd5 = md5()
        newMd5.update( clearpasswd )
        newCryptPasswd = newMd5.hexdigest()
        return newCryptPasswd
    
    
    def setPasswd( self, clearPasswd ):
        newCryptPasswd = self.crypt( clearPasswd )
        transaction = self._getTransaction( Transaction.WRITE )
        try:
            transaction.setPasswd( newCryptPasswd )
            self.newPasswd = ( clearPasswd , newCryptPasswd )
            transaction.commit()
        except Exception , err:
            transaction.rollback()
            raise err
            
    def sendNewPasswd(self):
        if not self.newPasswd:
            raise MobyleError , "password has not been set"

        mail = Email( self.user_email ) 
        mail.send( 'NEW_PASSWD' , {'SENDER'     : cfg.sender() ,
                                   'HELP'       : cfg.mailHelp() ,
                                   'SERVER_NAME': cfg.portal_url() ,
                                   'PASSWD'     : self.newPasswd[0],          
                                   }  )


    def status(self):
        """
        """
        email , authenticated , activated = self.getBaseInfo()
        return """
    email          %s
    session        %s
    authenticated  %s
    activated      %s
    """ %( email , self.key , authenticated , activated )
    
    
    def inactivate(self):
        transaction = self._getTransaction( Transaction.WRITE)
        try:
            transaction.inactivate()
            transaction.commit()
        except Exception , err :
            transaction.rollback()
            raise err
            
            
    def activate(self):
        transaction = self._getTransaction( Transaction.WRITE)
        try:
            transaction.activate()
            transaction.commit()
        except Exception , err :
            transaction.rollback()
            raise err
    
    def strenght_meter(self , passwd ):
        score = 1
        if not passwd:
            # the password is empty
            return 0
        if len( passwd ) < self.PASS_MIN_LEN :
            # a short password canot be stronger than 1
            return 1
        if len( passwd ) >= self.PASS_MIN_LEN :
            score += 1
        if passwd.lower() != passwd and passwd.upper() != passwd:
            #the password mix upper and lower case
            score += 1
        for x in passwd:
            #the password contains at least one digits
            if x.isdigit(): 
                score += 1
                break
        for x in passwd:
            #the password contains at least one symbols (not digits not letters)
            if not x.isalnum() :
                score += 1
                break
        return score

    def _findSession( self ):
        newMd5 = md5()
        newMd5.update( str( self.user_email ) )
        key = newMd5.hexdigest()
        sessionDir = os.path.join( cfg.user_sessions_path() , AuthenticatedSession.DIRNAME , key )
        if os.path.exists( sessionDir ):
            return sessionDir , key
        else:
            raise SessionError , "there no session corresonding to %s" % self.user_email
        
if __name__ == '__main__':
    from getpass import getpass
    from optparse import OptionParser
    
    usage = """usage: %prog [options] email
    
change session password for email
    """
    parser = OptionParser( usage= usage )
    parser.add_option( "-s" , "--status",
                       action = "store_true", 
                       dest = "status",
                       default = False ,
                       help = "display session status.")
    
    parser.add_option( "-f" , "--force",
                       action = "store_true", 
                       dest = "force",
                       default = False ,
                       help = "force new password even if strenght is weak.")
    
    parser.add_option( "-m" , "--mail",
                       action = "store_true", 
                       dest = "mail",
                       default = False ,
                       help = "send new password by email.")
    
    parser.add_option( "-a" , "--activate",
                       action = "store_true", 
                       dest = "activate",
                       default = False ,
                       help = "activate session.")
    
    parser.add_option( "-i" , "--inactivate",
                       action = "store_true", 
                       dest = "inactivate",
                       default = False ,
                       help = "inactivate session.")
    
    options, args = parser.parse_args() 

    if  options.activate and options.inactivate:
        print >> sys.stderr , "options -i and -a are not compatble"
        parser.print_help( sys.stderr )
        sys.exit(1)
    try:
        email = args[0]
    except IndexError:
        print >> sys.stderr , "You must provide an email."
        parser.print_help( sys.stderr )
        sys.exit(1)
    
    cfg = Config()    
    email = EmailAddress( email )
    email_checked = email.check()
    if not email_checked:
        msg = email.getMessage()
        print >> sys.stderr , msg
        sys.exit(2)       
    
    if options.status:
        try:
            sPasswd = SessionPasswd( email , cfg )
        except SessionError , err :
            print >> sys.stderr , err
            sys.exit(1)
            
        email , isAuthenticated , isActivated = sPasswd.getBaseInfo()
        print >> sys.stdout , """
email          %s
session        %s
authenticated  %s
activated      %s        
""" % ( email , sPasswd.key , isAuthenticated , isActivated )
        sys.exit(0)
         
    if options.activate:
        sPasswd = SessionPasswd( email , cfg )
        sPasswd.activate()
    elif options.inactivate:
        sPasswd = SessionPasswd( email , cfg )
        sPasswd.inactivate()
    else:
        print >> sys.stdout ,"Changing passowrd for %s" % email 
        sPasswd = SessionPasswd( email , cfg )
        passwd = getpass( "New password : " )
        confirm = getpass( "Retype new password : " )
        if passwd != confirm:
            print >> sys.stderr , "Sorry, passwords do not match."
            sys.exit(3)
        strenght = sPasswd.strenght_meter( passwd )
        if strenght < 4 and not options.force:
            print >> sys.stderr , """BAD PASSWORD: password strenght is %s
Choose a password 
  - with %d min lenght
  - mix upper and lower case
  - use digits and symbols """ % ( sPasswd.STRENGTH[ strenght ] , sPasswd.PASS_MIN_LEN )
        else:
            try:
                sPasswd.setPasswd( passwd )
                print >> sys.stderr , "the password for user %s has been updated successfully" % email
            except Exception ,err:
                print >> sys.stderr , "changing password for user %s aborted :\n %s" %( email , err )
                sys.exit(4)
            if options.mail:
                try:   
                    sPasswd.sendNewPasswd()
                    print >> sys.stderr , "the new password for user %s has been send successfully" % email
                except Exception , err :
                    print >> sys.stderr , "the new password cannot be send to %s :\n %s" %( email , err )
                    sys.exit(5)
                
  

        
    
