'''
$Id: User.py,v 1.15 2000/05/29 08:43:53 zen Exp $
'''

import Globals, App.Undo

from string import split,join
from base64 import decodestring, encodestring
from urllib import quote, unquote
from AccessControl.User import BasicUser, _remote_user_mode, domainSpecMatch
from AccessControl.PermissionRole import _what_not_even_god_should_do

import types
import whrandom
from sys import exc_info
from zLOG import LOG,ERROR,INFO,BLATHER

from time import time
import sha

def _debug(msg): pass
#def _debug(msg): LOG('GUF',0,msg)

class User(BasicUser):

    def __init__(self, name, guf):
	self.name   =name
	self._guf = guf
	self._getRolesRecurse = 0
	self._exptime = time()
	self._rolecache = None
	self._domaincache = None
	self.__ = None

    def getUserName(self):
        return self.name

    def _expire(self):
	t = time()
	guf = self._guf
	if t > self._exptime + guf.user_expires:
	    self._rolecache = None
	    self._domaincache = None
	    self.__ = None
	    self._exptime = t + guf.user_expires
	    return 1
	return 0

    def getRoles(self):
	'''Return a list of roles this user belongs to'''
	self._expire()

	if self._rolecache is not None: 
	    return self._rolecache

	# If our userRoles method needs to access another method,
	# Zope will call this again, so we need to detect this to
	# avoid infinite recursion and core dump. Returns a dummy
	# role. In this special case, you need to assign a proxy
	# role to the userRoles method so *it* has the required rights,
	# as since we have no authenticated user yet, there is nowhere else
	# to get the rights from. The alternative is to allow anonymous
	# access to the second method, which is not a good thing to
	# do with your security subsystem :-)
	if self._getRolesRecurse:
	    return ['Anonymous',]
	self._getRolesRecurse = 1

	if hasattr(self._guf,'userRoles'):
	    try:
		data = self._guf.userRoles(self._guf,
		    self._guf.REQUEST, username=self.name)

		if type(data) == types.StringType:
		    data = split(data)
		else:
		    data = list(data)
		    if len(data) > 0 and type(data[0]) != types.StringType:
			if hasattr(data[0],'role'):
			    data = map(lambda x: x.role, data)
			elif hasattr(data[0],'r'):
			    data = map(lambda x: x.r, data)

			if type(data[0]) != types.StringType:
			    raise 'Unsupported Type',\
				'userRoles returned an unsupported type'

	    except:
		try:
		    w = self._guf.userRoles.absolute_url()
		except:
		    w = 'userRoles'
		LOG('GUF',ERROR,'%s raised an exception (%s)' \
		    % (w,str(exc_info())),\
		    '',exc_info())
		return ['Anonymous',]

	    #if 'Anonymous' not in data:
	    #	data.insert(0,'Anonymous')
	    #	data.sort()

	    self._rolecache = data
	    return data

	else:
	    LOG('GUF',ERROR,
		'No userRoles method available - returning Anonymous')
	    return ['Anonymous',]

    
    def getDomains(self):
	'''Return a list of domains this user can log in from'''
	self._expire()

	if self._domaincache is not None: 
	    return self._domaincache

	if hasattr(self._guf,'userDomains'):
	    try:
		data = self._guf.userDomains(
		    self._guf, self._guf.REQUEST,
		    username=self.getUserName())

		if type(data) == types.StringType:
		    data = split(data)
		else:
		    data = list(data)

		    if len(data) > 0 and type(data[0]) != types.StringType:
			if hasattr(data[0],'domain'):
			    data = map(lambda x: x.domain, data)
			elif hasattr(data[0],'d'):
			    data = map(lambda x: x.d, data)

			if len(data) > 0 and type(data[0]) != types.StringType:
			    raise 'Unsupported Type',\
				'userDomains returned an unsupported type'
		
		if data and not self._guf.domainSpecValidate(data):
		    raise 'Illegal domain spec (%s) for %s' \
			% (str(data),self.getUserName())

	    except:
		try:
		    w = self._guf.userDomains.absolute_url()
		except:
		    w = 'userDomains'
		LOG('GUF',ERROR,'%s raised an exception (%s)' \
		    % (w,str(exc_info())),'',exc_info())
		return ['Anonymous',]

	    self._domaincache = data
	    return data

	else:
	    LOG('GUF',ERROR,
		'No userDomains method available - returning anywhere')
	    return []


    def authenticate(self, password, request):
	'''Authenticate a user'''

	self._expire()

	#_debug('Authenticating user %s' % self.getUserName())

	if not hasattr(self._guf,'userAuthenticate'):
	    LOG('GUF',ERROR,'No userAuthenticate method - %s auth failed' \
		% getUserName())
	    return 0

	if not self.has_permission('Can Login and Logout',self._guf):
	    return 0

	# Don't even bother checking password if we are from a dud
	# domain
	domains=self.getDomains()
	if domains and not domainSpecMatch(domains,request):
	    return 0

	# If we have a cached hash, we can shortcut
	# Don't bother logging, as we have already logged success
	# and we don't want too much spam
	if self.__ is not None:
	    return self.__ == sha.new(password).digest()

	roles = self.getRoles()

	try:
	    if self._guf.userAuthenticate(self._guf, request,
			    username=self.getUserName(), 
			    password=password,
			    roles=roles):
		# Log a message - this might spam if not using cookie
		# authentication? Is tune your logs a good response? I don't
		# want a toggle to turn this off via the web as it could ease
		# security breaches
		if self._guf.cookie_mode:
		    LOG('GUF',INFO, \
			'Successful authentication for user %s (%s)' \
			% (self.getUserName(),self._guf.absolute_url()))
		else:
		    # Lower log, as basic auth currently generates one
		    # of these for each access
		    LOG('GUF',BLATHER, \
			'Successful authentication for user %s (%s)' \
			% (self.getUserName(),self._guf.absolute_url()))

		self.__ = sha.new(password).digest() # Cache the hash only
		return 1
	except:
	    try:
		w = self._guf.userAuthenticate.absolute_url()
	    except:
		w = 'userAuthenticate'
	    LOG('GUF',ERROR,'%s raised an exception (%s)' \
		% (w,str(exc_info())),\
		'',exc_info())

	# Note we can't log failed authentication atempts here due to the
	# 'stacked' nature of Zope's UserFolders.
	return 0

Globals.default__class_init__(User)
