"""
    MoinMoin - Handle the Security String
    FrankieChow
"""
import os, hmac, string, random
from MoinMoin import caching

try:
   import cPickle as pickle
except ImportError:
   import pickle

# Set pickle protocol, see http://docs.python.org/lib/node64.html
try:
   # Requires 2.3
   PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
except AttributeError:
   # Use protocol 1, binary format compatible with all python versions
   PICKLE_PROTOCOL = 1


# Follow http://moinmoin.wikiwikiweb.de/MoinMoinBugs/Cookie_is_not_secure_enough
# This code is write by Nir Soffer

def gen(number):
    safe = string.ascii_letters + string.digits + '_-'
    return "%s" % (''.join([random.choice(safe) for i in range(number)]))

###

def luck():
    # ':=:' is FrankieChow luck string. maybe you can change this to
    #   self.cfg.site_luck_string
    return ':=:'
    
def make_security_key(securitystring, userid):
    """
    Make the hmac value for
      Key: securitystring
      msg: userid
    """
    return hmac.new(securitystring, userid).hexdigest()

class SecurityString:

    def __init__(self, request):

        # This is for cache for uid2security_string.
        arena = 'user'
        key = 'uid2security_hmac_string'
        self.cache = caching.CacheEntry(request, arena, key)
	self.request = request
        # First load the cache do auth.
	self.uid2security_hmac = self._load_cache()

    def _load_cache(self):
        try:
           return pickle.loads(self.cache.content())
        except: 
           return {}
    
    def _update_cache(self):
        self.cache.update( pickle.dumps(self.uid2security_hmac, PICKLE_PROTOCOL) )
    
    # Update the cache of uid2security_hmac_string, 
    #    don't need pass the user class obj.
    def update_uid2security_hmac_string_cache(self, storage_securitystring, storage_uid):
	update_security_key = make_security_key(storage_securitystring, storage_uid)
	if self.uid2security_hmac.has_key(storage_uid):
	    if self.uid2security_hmac[storage_uid] == update_security_key:
	        return None
        self.uid2security_hmac[storage_uid] = update_security_key
        self._update_cache()
    
    def cal_security_userid(self, security_cookie, user):
        """
	pass the security_cookie and return the user.id or None
        """
        # storage the user Obj and Request.
	self.user = user
    
        # Please care about the cookie syntax is change Maybe it have bug.
	try:
            self.hmac_string, self.uid = security_cookie.split( luck() )[:2]
	except:
	    return None
    
        # In here. the cache hasn't user info.
        #   Please check it more then one time.
        if self.uid2security_hmac.has_key(self.uid):
            # user can use the old cookie contents to login MoinMoin.
       	    # But, when the user modifty the security_string.
       	    #   after see more then one page ( Not only UserPreferences )
    	    #   then cache contents will change.
            if self.uid2security_hmac[self.uid] == self.hmac_string:
                return self.uid
            else:
    	        # If cann't auth by cache. then will find the security_string in user's
    	        # datafile. and do the auth again
    	        if self._validateSecurityString():
    	           # If can success auth. then update the cache.
    	           self.uid2security_hmac[self.uid] = self.hmac_string
    	           self._update_cache()
    	           return self.uid
    	        else: return None
        else:
            if self._validateSecurityString():
    	        self.uid2security_hmac[self.uid] = self.hmac_string
    	        self._update_cache()
                return self.uid
            else: return None
    
    def _validateSecurityString(self):
        # Get UserList
        userslist = self.user.getUserList(self.request)
        if self.uid in userslist:
           thisuser = self.user.User(self.request, id=self.uid)
           thisuser.load_from_id()
           securitystring = thisuser.security_string
           if self.hmac_string == make_security_key(securitystring, self.uid):
               return True
           else: return False
        else: return False
