# Zope User Folder for ScoDoc
# Adapte de l'Extensible User Folder
# simplifie pour les besoins de ScoDoc.
# Emmanuel Viennet 2013

#
# Extensible User Folder
#
# (C) Copyright 2000,2001 The Internet (Aust) Pty Ltd
# ACN: 082 081 472  ABN: 83 082 081 472
# All Rights Reserved
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# Author: Andrew Milton <akm@theinternet.com.au>
# $Id: exUserFolder.py,v 1.93 2004/11/10 14:15:33 akm Exp $

##############################################################################
#
# Zope Public License (ZPL) Version 0.9.4
# ---------------------------------------
# 
# Copyright (c) Digital Creations.  All rights reserved.
# 
# Redistribution and use in source and binary forms, with or
# without modification, are permitted provided that the following
# conditions are met:
# 
# 1. Redistributions in source code must retain the above
#    copyright notice, this list of conditions, and the following
#    disclaimer.
# 
# 6. Redistributions of any form whatsoever must retain the
#    following acknowledgment:
# 
#      "This product includes software developed by Digital
#      Creations for use in the Z Object Publishing Environment
#      (http://www.zope.org/)."
# 
# Disclaimer
# 
#   THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND
#   ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
#   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
#   SHALL DIGITAL CREATIONS OR ITS CONTRIBUTORS BE LIABLE FOR ANY
#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#   ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
#   IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
#   THE POSSIBILITY OF SUCH DAMAGE.
#
##############################################################################

# Portions Copyright (c) 2002 Nuxeo SARL <http://nuxeo.com>,
#          Copyright (c) 2002 Florent Guillaume <mailto:fg@nuxeo.com>.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in
#    the documentation and/or other materials provided with the
#    distribution.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import Globals, App.Undo, socket, os, string, sha, random, sys, zLOG

from Globals import DTMLFile, PersistentMapping
from string import join,strip,split,lower,upper,find

from OFS.Folder import Folder
from OFS.CopySupport import CopyContainer

from base64 import decodestring, encodestring
from urllib import quote, unquote

from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
from AccessControl.Role import RoleManager
from AccessControl.User import BasicUser, BasicUserFolder, readUserAccessFile
from AccessControl.PermissionRole import PermissionRole
from AccessControl.ZopeSecurityPolicy import _noroles
from OFS.DTMLMethod import DTMLMethod
from time import time
from OFS.ObjectManager import REPLACEABLE
from Persistence import Persistent

from PropertyEditor import *

from User import User, AnonUser
from UserCache.UserCache import GlobalUserCache, GlobalNegativeUserCache, GlobalAdvancedCookieCache, SessionExpiredException

from LoginRequiredMessages import LoginRequiredMessages

from AccessControl import Unauthorized

class LoginRequired(Exception):
    """Login required"""
    pass


# If there is no NUG Product just define a dummy class
try:
	from Products.NuxUserGroups.UserFolderWithGroups import BasicGroupFolderMixin, _marker
except ImportError:
	class BasicGroupFolderMixin:
		pass
	_marker = None

# Little function to create temp usernames
def createTempName():
	t=time()
	t1=time()
	t2=time()
	t3 = 0.0
	t3 = (t + t1 + t2) / 3
	un = "Anonymous %.0f"%(t3)
	return(un)


manage_addexUserFolderForm=DTMLFile('dtml/manage_addexUserFolder', globals(), __name__='manage_addexUserFolderForm')



def manage_addexUserFolder(self, authId, propId, memberId,
						   cookie_mode=0, session_length=0,
						   not_session_length=0,
						   sessionTracking=None, idleTimeout=None,
						   REQUEST={}, groupId=None, cryptoId=None):
	""" """
	if hasattr(self.aq_base, 'acl_users'):
		return Globals.MessageDialog(self,REQUEST,
			title  ='Item Exists',
			message='This object already contains a User Folder',
			action ='%s/manage_main' % REQUEST['URL1'])
	ob=exUserFolder(authId, propId, memberId, groupId, cryptoId, cookie_mode,
					session_length, sessionTracking, idleTimeout,
					not_session_length)
			
	self._setObject('acl_users', ob, None, None, 0)
	self.__allow_groups__=self.acl_users
	ob=getattr(self, 'acl_users')
	ob.postInitialisation(REQUEST)

	if REQUEST:
		return self.manage_main(self, REQUEST)
	return ''

#
# Module level caches
#
XUFUserCache=GlobalUserCache()
XUFNotUserCache=GlobalNegativeUserCache()
XUFCookieCache=GlobalAdvancedCookieCache()

class exUserFolder(Folder,BasicUserFolder,BasicGroupFolderMixin,
				   CopyContainer):
	""" """

	# HACK! We use this meta_type internally so we can be pasted into
	# the root. We registered with 'exUserFolder' meta_type however, so
	# our constructors work.
	meta_type='User Folder'
	id       ='acl_users'
	title    ='Extensible User Folder'
	icon     ='misc_/exUserFolder/exUserFolder.gif'

	isPrincipiaFolderish=1
	isAUserFolder=1
	__allow_access_to_unprotected_subobjects__=1
	authSources={}
	propSources={}
	cryptoSources={}
	membershipSources={}
	groupSources={} # UNUSED by ScoDoc

	manage_options=(
		{'label':'Users',      'action':'manage_main'},
		{'label':'Groups',	   'action':'manage_userGroups'},
		{'label':'Parameters', 'action':'manage_editexUserFolderForm'},
		{'label':'Authentication Source','action':'manage_editAuthSourceForm'},
		{'label':'Properties Source','action':'manage_editPropSourceForm'},
		{'label':'Membership Source', 'action':'manage_editMembershipSourceForm'},
		{'label':'Cache Data', 'action':'manage_showCacheData'},
		{'label':'Security',   'action':'manage_access'},
		{'label':'Contents',   'action':'manage_contents'},
		{'label':'Ownership',  'action':'manage_owner'},
		{'label':'Undo',       'action':'manage_UndoForm'},
		)

	__ac_permissions__=(
		('View management screens', ('manage','manage_menu','manage_main',
									 'manage_copyright', 'manage_tabs',
									 'manage_properties', 'manage_UndoForm',
									 'manage_edit', 'manage_contents',
									 'manage_cutObjects','manage_copyObjects',
									 'manage_pasteObjects',
									 'manage_renameForm',
									 'manage_renameObject',
									 'manage_renameObjects', ),
		 ('Manager',)),
		
		('Undo changes',            ('manage_undo_transactions',),
		 ('Manager',)),
		
		('Change permissions',      ('manage_access',),
		 ('Manager',)),
		
		('Manage users',            ('manage_users', 'manage_editUserForm',
									 'manage_editUser', 'manage_addUserForm',
									 'manage_addUser', 'manage_userActions',
									 'userFolderAddGroup',
									 'userFolderDelGroups',
									 'getGroupNames',
					                                 'getGroupById',
								         'manage_userGroups',
									 'manage_addGroup',
									 'manage_showGroup',),
		 ('Manager',)),
		
		('Change exUser Folders',   ('manage_edit',),
		 ('Manager',)),
		
		('View',                    ('manage_changePassword',
									 'manage_forgotPassword', 'docLogin','docLoginRedirect',
									 'docLogout', 'logout', 'DialogHeader',
									 'DialogFooter', 'manage_signupUser',
									 'MessageDialog', 'redirectToLogin','manage_changeProps'),
		 ('Anonymous', 'Authenticated', 'Manager')),
		
		('Manage properties',       ('manage_addProperty',
									 'manage_editProperties',
									 'manage_delProperties',
									 'manage_changeProperties',
									 'manage_propertiesForm',
									 'manage_propertyTypeForm',
									 'manage_changePropertyTypes',
									 ),
		 ('Manager',)),
		('Access contents information', ('hasProperty', 'propertyIds',
										 'propertyValues','propertyItems',
										 'getProperty', 'getPropertyType',
										 'propertyMap', 'docLogin','docLoginRedirect',
										 'DialogHeader', 'DialogFooter',
										 'MessageDialog', 'redirectToLogin',),
		 ('Anonymous', 'Authenticated', 'Manager')),
		)
	manage_access=DTMLFile('dtml/access',globals())
	manage_tabs=DTMLFile('common/manage_tabs',globals())
	manage_properties=DTMLFile('dtml/properties', globals())
	manage_main=DTMLFile('dtml/mainUser', globals())
	manage_contents=Folder.manage_main
	manage_showCacheData=DTMLFile('dtml/manage_showCacheData', globals())

	# This is going away soon...
	docLoginRedirect=DTMLFile('dtml/docLoginRedirect', globals())	

	# Stupid crap
	try:
		manage_contents._setName('manage_contents')
	except AttributeError:
		pass


	MessageDialog=DTMLFile('common/MessageDialog', globals())
	MessageDialog.__replaceable__ = REPLACEABLE
	
	manage_addUserForm=DTMLFile('dtml/manage_addUserForm',globals())
	manage_editUserForm=DTMLFile('dtml/manage_editUserForm',globals())

	DialogHeader__roles__=()
	DialogHeader=DTMLFile('common/DialogHeader',globals())
	DialogFooter__roles__=()
	DialogFooter=DTMLFile('common/DialogFooter',globals())

	manage_editAuthSourceForm=DTMLFile('dtml/manage_editAuthSourceForm',globals())
	manage_editPropSourceForm=DTMLFile('dtml/manage_editPropSourceForm',globals())
	manage_editMembershipSourceForm=DTMLFile('dtml/manage_editMembershipSourceForm', globals())

	manage_addPropertyForm=DTMLFile('dtml/manage_addPropertyForm', globals())
	manage_createPropertyForm=DTMLFile('dtml/manage_createPropertyForm', globals())
	manage_editUserPropertyForm=DTMLFile('dtml/manage_editUserPropertyForm', globals())

	manage_editexUserFolderForm=DTMLFile('dtml/manage_editexUserFolderForm', globals())

	manage_userGroups=DTMLFile('dtml/mainGroup',globals())


	# Use pages from NUG if it's there, otherwise no group support
	try:
		manage_addGroup = BasicGroupFolderMixin.manage_addGroup
		manage_showGroup = BasicGroupFolderMixin.manage_showGroup
	except:
		manage_addGroup = None
		manage_showGroup = None

	# No more class globals
	
	# sessionLength=0 # Upgrading users should get no caching.
	# notSessionLength=0 # bad cache limit
	# cookie_mode=0
	# sessionTracking=None # Or session tracking.
	# idleTimeout=0
	
	def __init__(self, authId, propId, memberId, groupId, cryptoId,
				 cookie_mode=0, session_length=0, sessionTracking=None,
				 idleTimeout=0, not_session_length=0):
		self.cookie_mode=cookie_mode
		self.sessionLength=session_length
		self.notSessionLength=not_session_length
		self.sessionTracking=sessionTracking
		self.idleTimeout=idleTimeout
		
		_docLogin=DTMLFile('dtml/docLogin',globals())
		_docLogout=DTMLFile('dtml/docLogout',globals())

		docLogin=DTMLMethod(__name__='docLogin')
		docLogin.manage_edit(data=_docLogin, title='Login Page')
		self._setObject('docLogin', docLogin, None, None, 0)

		docLogout=DTMLMethod(__name__='docLogout')
		docLogout.manage_edit(data=_docLogout, title='Logout Page')
		self._setObject('docLogout', docLogout, None, None, 0)

		postUserCreate=DTMLMethod(__name__='postUserCreate')
		postUserCreate.manage_edit(data=_postUserCreate, title='Post User Creation methods')
		self._setObject('postUserCreate', postUserCreate, None, None, 0)

		self.manage_addAuthSource=self.authSources[authId].manage_addMethod
		self.manage_addPropSource=self.propSources[propId].manage_addMethod
		self.manage_addMembershipSource=self.membershipSources[memberId].manage_addMethod

		self.manage_addGroupSource=None # UNUSED by ScoDoc
		self.currentGroupsSource=None

		if cryptoId:
			self.cryptoId = cryptoId
		else:
			self.cryptoId = 'Crypt'
			
	def __setstate__(self, state):
		Persistent.__setstate__(self, state)
		if not hasattr(self, 'currentGroupSource'):
			self.currentGroupSource = None
		if not hasattr(self, 'sessionLength'):
			self.sessionLength = 0
		if not hasattr(self, 'notSessionLength'):
			self.notSessionLength = 0
		if not hasattr(self, 'cookie_mode'):
			self.cookie_mode = 0
		if not hasattr(self, 'sessionTraining'):
			self.sessionTracking = None
		if not hasattr(self, 'idleTimeout'):
			self.idleTimeout=0

	def manage_beforeDelete(self, item, container):
		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Attempting to delete an exUserFolder instance")
		if item is self:
			try:
				self.cache_deleteCache()
				self.xcache_deleteCache()
				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Caches deleted")
			except:
				#pass
				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- Cache deletion failed")

			try:
				del container.__allow_groups__
				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deleted")
			except:
				#pass
				zLOG.LOG("exUserFolder", zLOG.BLATHER, "-- container.__allow_groups_ deletion failed")


	def manage_afterAdd(self, item, container):
		zLOG.LOG("exUserFolder", zLOG.BLATHER, "Adding an exUserFolder")
                         
		if item is self:
			if hasattr(self, 'aq_base'): self=self.aq_base
			container.__allow_groups__=self

	def manage_editPropSource(self, REQUEST):
		""" Edit Prop Source """
		if self.currentPropSource:
			self.currentPropSource.manage_editPropSource(REQUEST)
		return self.manage_main(self, REQUEST)

	def manage_editAuthSource(self, REQUEST):
		""" Edit Auth Source """
		self.currentAuthSource.manage_editAuthSource(REQUEST)
		return self.manage_main(self, REQUEST)

	def manage_editMembershipSource(self, REQUEST):
		""" Edit Membership Source """
		if self.currentMembershipSource:
			return self.currentMembershipSource.manage_editMembershipSource(REQUEST)

	def postInitialisation(self, REQUEST):
		self.manage_addAuthSource(self=self,REQUEST=REQUEST)
		self.manage_addPropSource(self=self,REQUEST=REQUEST)
		self.manage_addMembershipSource(self=self,REQUEST=REQUEST)
		self.currentGroupSource = None
	
	def addAuthSource(self, REQUEST={}):
		return self.manage_addAuthSourceForm(self, REQUEST)

	def addPropSource(self, REQUEST={}):
		return self.manage_addPropSourceForm(self, REQUEST)

	def addMembershipSource(self, REQUEST={}):
		return self.manage_editMembershipSourceForm(self, REQUEST)

	def listUserProperties(self, username):
		if self.currentPropSource:
			return self.currentPropSource.listUserProperties(username=username)

	def getUserProperty(self, username, key):
		if self.currentPropSource:
			return self.currentPropSource.getUserProperty(key=key, username=username)
	
	def reqattr(self, request, attr, default=None):
		try:    return request[attr]
		except: return default

	def getAuthFailedMessage(self, code):
		""" Return a code """
		if LoginRequiredMessages.has_key(code):
			return LoginRequiredMessages[code]
		return 'Login Required'

	# Called when we are deleted
	def cache_deleteCache(self):
		pp = string.join(self.getPhysicalPath(), '/')
		XUFUserCache.deleteCache(pp)
		
	def cache_addToCache(self, username, password, user):
		if not self.sessionLength:
			return
		# fix by emmanuel
		if username == self._emergency_user.getUserName():
			return
		# /fix
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFUserCache.getCache(pp)
		if not x:
			x = XUFUserCache.createCache(pp, self.sessionLength)
		x.addToCache(username, password, user)

	def cache_getUser(self, username, password, checkpassword=1):
		if not self.sessionLength:
			return None
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFUserCache.getCache(pp)
		if not x:
			return None
		u = x.getUser(self, username, password, checkpassword)
		if u is not None:
			u = u.__of__(self)
		return u

	def cache_removeUser(self, username):
		if not self.sessionLength:
			return
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFUserCache.getCache(pp)
		if x:
			x.removeUser(username)

	def cache_getCacheStats(self):
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFUserCache.getCache(pp)
		if not x:
			x = XUFUserCache.createCache(pp, self.sessionLength)			
		if x:
			return x.getCacheStats()

	def cache_getCurrentUsers(self):
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFUserCache.getCache(pp)
		if x:
			return x.getCurrentUsers(self)

	# negative cache functions
	def xcache_deleteCache(self):
		pp = string.join(self.getPhysicalPath(), '/')
		XUFNotUserCache.deleteCache(pp)
		
	def xcache_addToCache(self, username):
		if not self.notSessionLength:
			return
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFNotUserCache.getCache(pp)
		if not x:
			x = XUFNotUserCache.createCache(pp, self.notSessionLength)
		x.addToCache(username)

	def xcache_getUser(self, username):
		if not self.notSessionLength:
			return None
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFNotUserCache.getCache(pp)
		if not x:
			return None
		return x.getUser(username)

	def xcache_removeUser(self, username):
		#zLOG.LOG('exUserFolder', zLOG.PANIC, 'xcache_removeUser(%s)' % username)
		if not self.notSessionLength:
			return
		pp = string.join(self.getPhysicalPath(), '/')
		x = XUFNotUserCache.getCache(pp)
		if x:
			#zLOG.LOG('exUserFolder', zLOG.PANIC, 'xcache_removeUser removing')
			x.removeUser(username)

	# Cookie Cache Functions
	def cache_deleteCookieCache(self):
		pp = string.join(self.getPhysicalPath(), '/')
		XUFCookieCache.deleteCache(pp)

	def cache_addToCookieCache(self, username, password, key):
		pp = string.join(self.getPhysicalPath(), '/')
		c = XUFCookieCache.getCache(pp)
		if not c:
			c = XUFCookieCache.createCache(pp, 86400)
		c.addToCache(username, password, key)

	def cache_getCookieCacheUser(self, key):
		pp = string.join(self.getPhysicalPath(), '/')
		c = XUFCookieCache.getCache(pp)
		if not c:
			return None
		return c.getUser(key)

	def cache_removeCookieCacheUser(self, key):
		pp = string.join(self.getPhysicalPath(), '/')
		c = XUFCookieCache.getCache(pp)
		if c:
			c.removeUser(key)
    
	def manage_editUser(self, username, REQUEST={}): # UNUSED by ScoDoc
 		""" Edit a User """
		# username=self.reqattr(REQUEST,'username')
		password=self.reqattr(REQUEST,'password')
		password_confirm=self.reqattr(REQUEST,'password_confirm')
		roles=self.reqattr(REQUEST,'roles', [])
		groups=self.reqattr(REQUEST, 'groupnames', [])
        
		if not username:
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='A username must be specified',
				action ='manage_main')

		if (password or password_confirm) and (password != password_confirm):
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='Password and confirmation do not match',
				action ='manage_main')
		
		self._doChangeUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
		
		return self.MessageDialog(self,REQUEST=REQUEST,
			title = 'User Updated',
			message= 'User %s was updated.'%(username),
			action = 'manage_main')
    
    
    # Methode special pour ScoDoc: evite le code inutile dans notre contexte
    # et accede a la BD via le curseur psycopg2 fourni
    # (facilitera la separation de Zope)
	def scodoc_editUser(self, cursor, username, password=None, roles=[]):
		"""Edit a ScoDoc user"""
		roles = list(roles)
		rolestring= ','.join(roles)
		# Don't change passwords if it's null
		if password:
			secret=self.cryptPassword(username, password)
			# Update just the password:			   
			# self.sqlUpdateUserPassword(username=username, password=secret)
			cursor.execute("UPDATE sco_users SET passwd=%(secret)s WHERE user_name=%(username)s",
						   { 'secret':secret, 'username': username } )
		
		#self.sqlUpdateUser(username=username, roles=rolestring)
		cursor.execute("UPDATE sco_users SET roles=%(rolestring)s WHERE user_name=%(username)s",
					   { 'rolestring':rolestring, 'username': username } )

		if hasattr(self.currentAuthSource, '_v_lastUser'):
			# Specific for pgAuthSource:
			self.currentAuthSource._v_lastUser={} # clear pg user cache
		
		# We may have updated roles or passwords... flush the user...
		self.cache_removeUser(username)
		self.xcache_removeUser(username)
    
	#
	# Membership helper
	#
	def goHome(self, REQUEST, RESPONSE):
		""" Go to home directory """
		if self.currentMembershipSource:
			self.currentMembershipSource.goHome(REQUEST, RESPONSE)


	# 
	# Membership method of changing user properties
	# 

	def manage_changeProps(self, REQUEST):
		""" Change Properties """
		if self.currentMembershipSource:
			return self.currentMembershipSource.changeProperties(REQUEST)
		else:
			
			return self.MessageDialog(self,REQUEST,
				title = 'This is a test',
				message= 'This was a test',
				action = '..')
 

	#
	# Membership method of adding a new user.
	# If everything goes well the membership plugin calls manage_addUser()
	#
	
	def manage_signupUser(self, REQUEST):
		""" Signup a new user """
		""" This is seperate so you can add users using the normal """
		""" interface w/o going through membership policy """

		username=self.reqattr(REQUEST,'username')
		roles=self.reqattr(REQUEST,'roles')

		if not username:
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='A username must be specified',
				action ='manage_main')

		if (self.getUser(username) or
			(self._emergency_user and
			 username == self._emergency_user.getUserName())):
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='A user with the specified name already exists',
				action ='manage_main')

		if self.currentMembershipSource:
			return self.currentMembershipSource.createUser(REQUEST)

	#
	# Membership method of changing passwords
	#
	def manage_changePassword(self, REQUEST):
		""" Change a password """
		if self.currentMembershipSource:
			return self.currentMembershipSource.changePassword(REQUEST)
		
	#
	# User says they can't remember their password
	#
	def manage_forgotPassword(self, REQUEST):
		""" So something about forgetting your password """
		if self.currentMembershipSource:
			return self.currentMembershipSource.forgotPassword(REQUEST)
		
	def __creatable_by_emergency_user__(self): return 1

	def manage_addUser(self, REQUEST):
		""" Add a New User """
		username=self.reqattr(REQUEST,'username')
		password=self.reqattr(REQUEST,'password')
		password_confirm=self.reqattr(REQUEST,'password_confirm')
		roles=self.reqattr(REQUEST,'roles')
		groups=self.reqattr(REQUEST, 'groupnames', [])

		if not username:
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='A username must be specified',
				action ='manage_main')

		if not password or not password_confirm:
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='Password and confirmation must be specified',
				action ='manage_main')

		if (self.getUser(username) or
			(self._emergency_user and
			 username == self._emergency_user.getUserName())):
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='A user with the specified name already exists',
				action ='manage_main')

		if (password or password_confirm) and (password != password_confirm):
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Illegal value', 
				message='Password and confirmation do not match',
				action ='manage_main')

		self._doAddUser(username, password, roles, domains='', groups=groups, REQUEST=REQUEST)
		#
		# Explicitly check our contents, do not just acquire postUserCreate
		#
		if 'postUserCreate' in self.objectIds():
			self.postUserCreate(self, REQUEST)
		
		return self.MessageDialog(self,REQUEST=REQUEST,
			title = 'User Created',
			message= 'User %s was created.'%(username),
			action = 'manage_main')

	def _doAddUser(self, name, password, roles, domains='', groups=(), **kw):
		""" For programatically adding simple users """
		self.currentAuthSource.createUser(name, password, roles)
		if self.currentPropSource:
			# copy items not in kw from REQUEST
			REQUEST = kw.get('REQUEST', self.REQUEST)
			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
			self.currentPropSource.createUser(name, kw)

	def _doChangeUser(self, name, password, roles, domains='', groups=(), **kw):
		self.currentAuthSource.updateUser(name, password, roles)
		if self.currentPropSource:
			# copy items not in kw from REQUEST
			REQUEST = kw.get('REQUEST', self.REQUEST)
			map(kw.setdefault, REQUEST.keys(), REQUEST.values())
			self.currentPropSource.updateUser(name, kw)
		# We may have updated roles or passwords... flush the user...
		self.cache_removeUser(name)
		self.xcache_removeUser(name)
		
	def _doDelUsers(self, names):
		self.deleteUsers(names)

	def _createInitialUser(self):
		if len(self.getUserNames()) <= 1:
			info = readUserAccessFile('inituser')
			if info:
				name, password, domains, remote_user_mode = info
				self._doAddUser(name, password, ('Manager',), domains)


	def getUsers(self):
		"""Return a list of user objects or [] if no users exist"""
		data=[]
		try:
			items=self.listUsers()
			for people in items:
				user=User({'name':		people['username'],
						   'password':	people['password'],
						   'roles':		people['roles'], 
						   'domains':	''},
						  self.currentPropSource,
						  self.cryptPassword,
						  self.currentAuthSource,
						  self.currentGroupSource)
				data.append(user)
		except:
			import traceback
			traceback.print_exc()
			pass
			
		return data

	getUsers__roles__=('Anonymous','Authenticated')
	
	def getUser(self, name):
		"""Return the named user object or None if no such user exists"""
		user = self.cache_getUser(name, '', 0)
		#zLOG.LOG('exUserFolder.getUser', zLOG.PANIC, 'cache_getUser(%s)=%s' % (name,user))
		if user:
			return user
		try:
			items=self.listOneUser(name)
			#zLOG.LOG('exUserFolder.getUser', zLOG.PANIC, 'listOneUser=%s' % items) 
		except:
			zLOG.LOG("exUserFolder", zLOG.ERROR,
                                 "error trying to list user %s" % name,
                                 '',
                                 sys.exc_info())
			return None

		if not items:
			return None
		
		for people in items:
			user =  User({'name':    people['username'],
						  'password':people['password'],
						  'roles':   people['roles'],
						  'domains':	''},
						 self.currentPropSource,
						 self.cryptPassword,
						 self.currentAuthSource,
						 self.currentGroupSource)
			return user
		return None
		
	def manage_userActions(self, submit=None, userids=None, REQUEST={}):
		""" Do things to users """
		if submit==' Add ':
			if hasattr(self.currentAuthSource,'manage_addUserForm'):
				return self.currentAuthSource.manage_addUserForm(self, REQUEST)
			else:
				return self.manage_addUserForm(self, REQUEST)
		if submit==' Delete ':
			self.deleteUsers(userids)
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='Users Deleted',
				message='Selected Users have been deleted',
				action =REQUEST['URL1']+'/manage_main',
				target ='manage_main')

		if REQUEST:
			return self.manage_main(self,REQUEST)
		return ''

	def identify(self, auth):
		# Identify the username and password.  This is where new modes should
		# be called from, and if pluggable modes ever take shape, here ya go!

		if self.cookie_mode and not auth:
			# The identify signature does not include the request, sadly.
			# I think that's dumb.
			request = self.REQUEST
			response = request.RESPONSE
	
			if request.has_key('__ac_name') and request.has_key('__ac_password'):
				return request['__ac_name'], request['__ac_password']
			elif request.has_key('__ac') and self.cookie_mode == 1:
				return self.decodeBasicCookie(request, response)
			elif request.has_key('__aca') and self.cookie_mode == 2:
				return self.decodeAdvancedCookie(request, response)

		if auth and lower(auth[:6]) == 'basic ':
				return tuple(split(decodestring(split(auth)[-1]), ':', 1))

		return None, None

	def decodeUserCookie(self, request, response):
		return self.identify('')

	def validate(self, request, auth='', roles=_noroles):
		"""
		Perform identification, authentication, and authorization.
		"""
		# Called at each web request
		#zLOG.LOG('exUserFolder', zLOG.PANIC, 'validate')
		v = request['PUBLISHED']
		a, c, n, v = self._getobcontext(v, request)

		name, password = self.identify(auth) # decode cookie, and raises LoginRequired if no ident info
        # password is the cleartext passwd
		# zLOG.LOG('exUserFolder', zLOG.DEBUG, 'identify returned %s, %s' % (name, password))

		response = request.RESPONSE
		if name is not None:
			try:
				xcached_user = self.xcache_getUser(name)
				#zLOG.LOG('exUserFolder.validate', zLOG.PANIC, 'xcached_user=%s' % xcached_user)
				if xcached_user:
					#zLOG.LOG('exUserFolder.validate', zLOG.PANIC, 'returning None')
					return None
			except:
				zLOG.LOG('exUserFolder', zLOG.ERROR,
						 "error while looking up '%s' on the xcache" % name,
						 '',
						 sys.exc_info())

			user = self.authenticate(name, password, request)
			#zLOG.LOG('exUserFolder.validate', zLOG.PANIC, 'user=%s' % user) 
			if user is None:
				# If it's none, because there's no user by that name,
				# don't raise a login, allow it to go higher...
				# This kinda breaks for people putting in the wrong username
				# when the Folder above uses a different auth method.
				# But it doesn't lock Manager users out inside Zope.
				# Perhaps this should be a tunable.

				# modified by Emmanuel
				try:
					lou = self.listOneUser(name) 
				except:
					lou = None
				if lou:
					self.challenge(request, response, 'login_failed', auth)
				return None
			self.remember(name, password, request)
			self.cache_addToCache(name, password, user)
			emergency = self._emergency_user
			if emergency and user is emergency:
				if self._isTop():
					return emergency.__of__(self)
				else:
					return None
			if self.authorize(user, a, c, n, v, roles):
				return user.__of__(self)
			if self._isTop() and self.authorize(self._nobody, a, c, n, v, roles):
				return self._nobody.__of__(self)
			self.challenge(request, response, 'unauthorized')
			return None
		else:
			if self.sessionTracking and self.currentPropSource:
				user = self.createAnonymousUser(request, response)
				if self.authorize(user, a, c, n, v, roles):
					return user.__of__(self)
			if self.authorize(self._nobody, a, c, n, v, roles):
				if self._isTop():
					return self._nobody.__of__(self)
				else:
					return None
			else:
				self.challenge(request, response, None, auth)
				return None
	
	def authenticate(self, name, password, request):
		#zLOG.LOG('exUserFolder.authenticate', zLOG.PANIC, '%s %s' % (name, password)) 
		emergency = self._emergency_user
		if emergency and name == emergency.getUserName():
			return emergency
		try:
			user = self.cache_getUser(name, password)
			#zLOG.LOG('exUserFolder.authenticate', zLOG.PANIC, 'cache_getUser=%s' % user) 
			if user:
				return user
		except SessionExpiredException:
			if self.idleTimeout:
				self.logout(request)
				self.challenge(request, request.RESPONSE, 'session_expired')
				return None
		user = self.getUser(name)
		#zLOG.LOG('exUserFolder.authenticate', zLOG.PANIC, 'getUser=%s' % user) 
		if user is not None:
			if user.authenticate(self.currentAuthSource.listOneUser,
								 password,
								 request,
								 self.currentAuthSource.remoteAuthMethod):
				return user
		return None

	def challenge(self, request, response, reason_code='unauthorized',
				  auth=''):
		# Give whatever mode we're in a chance to challenge the validation
		# failure.  We do this to preserve LoginRequired behavior.  The
		# other thing we could do is let the None propagate on up and patch
		# the request's unauthorized method to 

		if self.cookie_mode and not auth:
			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'raising LoginRequired for %s' % reason_code)
			if reason_code == 'login_failed':
				response.expireCookie('__ac', path='/')
				response.expireCookie('__aca', path='/')
			if reason_code:
				request.set('authFailedCode', reason_code)
			raise LoginRequired(self.docLogin(self, request))
		else:
			zLOG.LOG('exUserFolder', zLOG.DEBUG, 'not raising LoginRequired for %s' % reason_code)

	def remember(self, name, password, request):
		response = request.RESPONSE
		if self.cookie_mode == 1:
			self.setBasicCookie(name, password, request, response)
		elif self.cookie_mode == 2:
			self.setAdvancedCookie(name, password, request, response)

		if self.cookie_mode:
			try:
				del request.form['__ac_name']
				del request.form['__ac_password']
			except KeyError:
				pass

	def makeRedirectPath(self):
		REQUEST=self.REQUEST
		if not REQUEST.has_key('destination'):
			script=REQUEST['SCRIPT_NAME']
			pathinfo=REQUEST['PATH_INFO']
			redirectstring=script+pathinfo
			if REQUEST.has_key('QUERY_STRING'):
				querystring='?'+quote(REQUEST['QUERY_STRING'])
				redirectstring=redirectstring+querystring

			REQUEST['destination']=redirectstring
		
	def redirectToLogin(self, REQUEST):
		""" Allow methods to call from Web """
		script=''
		pathinfo=''
		querystring=''
		redirectstring=''
		authFailedCode=''
		
		if not REQUEST.has_key('destination'):
			if self.currentMembershipSource:
				redirectstring = self.currentMembershipSource.getLoginDestination(REQUEST)
			else:
				script=REQUEST['SCRIPT_NAME']
				pathinfo=REQUEST['PATH_INFO']
				redirectstring=script+pathinfo
				if REQUEST.has_key('QUERY_STRING'):
					querystring='?'+REQUEST['QUERY_STRING']
					redirectstring=redirectstring+querystring

			REQUEST['destination']=redirectstring

		
		if REQUEST.has_key('authFailedCode'):
			authFailedCode='&authFailedCode='+REQUEST['authFailedCode']
		
			
			
		if self.currentMembershipSource and self.currentMembershipSource.loginPage:
			try:
				REQUEST.RESPONSE.redirect('%s/%s?destination=%s%s'%(self.currentMembershipSource.baseURL, self.currentMembershipSource.loginPage,REQUEST['destination'],authFailedCode))				
				return
			except:
				pass
		return self.docLogin(self,REQUEST)

	def decodeBasicCookie(self, request, response):
		c=request['__ac']
		c=unquote(c)
		try:
			c=decodestring(c)
		except:
			response.expireCookie('__ac', path='/')
			raise LoginRequired(self.docLogin(self, request))
		
		name,password=tuple(split(c, ':', 1))
		return name, password
		
	def decodeAdvancedCookie(self, request, response):
		c = ''
		try:
			c = request['__aca']
			c = unquote(c)
		except:
			response.expireCookie('__aca', path='/')
			response.expireCookie('__ac', path='/')	# Precaution
			response.flush()
			raise LoginRequired(self.docLogin(self, request))

		u = self.cache_getCookieCacheUser(c)
		if u:
			return u

		response.expireCookie('__aca', path='/')
		response.expireCookie('__ac', path='/')	# Precaution
		response.flush()
		raise LoginRequired(self.docLogin(self, request))

	def setBasicCookie(self, name, password, request, response):
		token='%s:%s' % (name, password)
		token=encodestring(token)
		token=quote(token)
		response.setCookie('__ac', token, path='/')
		request['__ac']=token
		
	def setAdvancedCookie(self, name, password, request, response):
		xufid = self._p_oid
		hash = encodestring(sha.new('%s%s%f%f%s'%(
			name, password, time(), random.random(), str(request))).digest())
		token=quote(hash)
		response.setCookie('__aca', token, path='/')
		response.flush()
		request['__aca']=token
		self.cache_addToCookieCache(name, password, hash)
		
	def setAnonCookie(self, name, request, resp):
		token='%s:%s' % (name, '')
		token=encodestring(token)
		token=quote(token)
		resp.setCookie('__ac', token, path='/')
		request['__ac']=token

	def createAnonymousUser(self, request, resp):
		aName=createTempName()
		bogusREQUEST={}
		bogusREQUEST['user_realname']='Guest User'
		self.currentPropSource.createUser(aName, bogusREQUEST)
		ob = AnonUser(aName, [], self.currentPropSource)
		ob = ob.__of__(self)
		self.cache_addToCache(aName, '', ob)			
		self.setAnonCookie(aName, request, resp)
		return ob
		
	def manage_edit(self, cookie_mode, session_length, sessionTracking=None,
					idleTimeout=0, not_session_length=0,
			                title=None,
					REQUEST=None):
		"""Change properties"""

		self.cookie_mode=cookie_mode
		self.sessionLength=session_length
		self.notSessionLength=not_session_length
		self.sessionTracking=sessionTracking
		self.idleTimeout=idleTimeout
		if title:
			self.title = title
		
		if REQUEST:
			return self.MessageDialog(self,REQUEST=REQUEST,
				title  ='exUserFolder Changed',
				message='exUserFolder properties have been updated',
				action =REQUEST['URL1']+'/manage_main',
				target ='manage_main')

	def logout(self, REQUEST):
		"""Logout"""
		try:
			self.cache_removeUser(REQUEST['AUTHENTICATED_USER'].getUserName())
		except:
			pass
		
		REQUEST['RESPONSE'].expireCookie('__ac', path='/')
		REQUEST.cookies['__ac']=''
		try:
			acc = REQUEST['__aca']
			self.cache_removeCookieCacheUser(acc)
			REQUEST.cookies['__aca']=''
		except:
			pass
		REQUEST['RESPONSE'].expireCookie('__aca', path='/')

		
		
		return self.docLogout(self, REQUEST)

	#
	# Methods to be supplied by Auth Source
	#
	def deleteUsers(self, userids):
		self.currentAuthSource.deleteUsers(userids)

		# Comment out to use Andreas' pgSchema
		if self.currentPropSource:
			self.currentPropSource.deleteUsers(userids)

		if self.currentGroupSource:
			self.currentGroupSource.deleteUsers(userids)
			

	def listUsers(self):
		return self.currentAuthSource.listUsers()

	def user_names(self):
		return self.currentAuthSource.listUserNames()
	
	def getUserNames(self):
		return self.currentAuthSource.listUserNames()

	def listOneUser(self,username):
		return self.currentAuthSource.listOneUser(username)

	def cryptPassword(self, username, password):
		if hasattr(aq_base(self.currentAuthSource), 'cryptPassword'):
			return self.currentAuthSource.cryptPassword(username, password)

		if hasattr(self, 'cryptoId'):
			return self.cryptoSources[self.cryptoId].plugin(self, username, password)
		return self.cryptoSources['Crypt'].plugin(self, username, password)

	def PropertyEditor(self):
		""" """
		if self.REQUEST.has_key(self.REQUEST['propName']):
			return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
		return PropertyEditor.EditMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)

	def PropertyView(self):
		""" """
		if self.REQUEST.has_key(self.REQUEST['propName']):
			return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], self.REQUEST[self.REQUEST['propName']])
		return PropertyEditor.ViewMethods[self.REQUEST['propType']](self.REQUEST['propName'], None)

	def manage_addUserProperty(self, username, propName, propValue, REQUEST):
		""" add a new property """
		self.currentPropSource.setUserProperty(propName, username, propValue)
		if hasattr(self.currentAuthSource,'manage_editUserForm'):
			return self.currentAuthSource.manage_editUserForm(self, REQUEST)
		else:
			return self.manage_editUserForm(self,REQUEST)

	def getUserCacheStats(self):
		""" Stats """
		if self.sessionLength:
			if self.cache_getCacheStats()['attempts']:
				return self.cache_getCacheStats()
		return None

	def getUserCacheUsers(self):
		""" Current Users """
		if self.sessionLength:
			return self.cache_getCurrentUsers()
		return None

	def userFolderAddGroup(self, groupname, title='', **kw):
		"""Creates a group"""
		if self.currentGroupSource:
			apply(self.currentGroupSource.addGroup, (groupname, title), kw)
	
	def userFolderDelGroups(self, groupnames):
		"""Deletes groups"""
		if self.currentGroupSource:
			for groupname in groupnames:
				self.currentGroupSource.delGroup(groupname)

	def getGroupNames(self):
		"""Returns a list of group names"""
		if self.currentGroupSource:
			return self.currentGroupSource.listGroups()
		else:
			return []


	def getGroupById(self, groupname, default=_marker):
		"""Returns the given group"""
		if self.currentGroupSource:
			group = self.currentGroupSource.getGroup(groupname, default)
			if group:
				return group.__of__(self)
			else:
				return None

	def setUsersOfGroup(self, usernames, groupname):
		"""Sets the users of the group"""
		if self.currentGroupSource:
			return self.currentGroupSource.setUsersOfGroup(usernames, groupname)

	def addUsersToGroup(self, usernames, groupname):
		"""Adds users to a group"""
		if self.currentGroupSource:
			return self.currentGroupSource.addUsersToGroup(usernames, groupname)

	def delUsersFromGroup(self, usernames, groupname):
		"""Deletes users from a group"""
		if self.currentGroupSource:
			return self.currentGroupSource.delUsersFromGroup(usernames, groupname)

	def setGroupsOfUser(self, groupnames, username):
		"""Sets the groups of a user"""
		if self.currentGroupSource:
			return self.currentGroupSource.setGroupsOfUser(groupnames, username)

	def addGroupsOfUser(self, groupnames, username):
		"""Add groups to a user"""
		if self.currentGroupSource:
			return self.currentGroupSource.addGroupsToUser(groupnames, username)

	def delGroupsOfUser(self, groupnames, username):
		"""Deletes groups from a user"""
		if self.currentGroupSource:
			return self.currentGroupSource.delGroupsFromUser(groupnames, username)

	# We lie.
	def hasUsers(self):
		return 1


def doAuthSourceForm(self,authId):
	""" la de da """
	return exUserFolder.authSources[authId].manage_addForm

def doPropSourceForm(self,propId):
	""" la de da """
	return exUserFolder.propSources[propId].manage_addForm

def doMembershipSourceForm(self, memberId):
	""" doot de doo """
	return exUserFolder.membershipSources[memberId].manage_addForm

#def doGroupSourceForm(self,groupId):
#	""" la de da """
#	return exUserFolder.groupSources[groupId].manage_addForm

def getAuthSources(self):
	""" Hrm I need a docstring """
	l=[]
	for o in exUserFolder.authSources.keys():
		l.append(
			exUserFolder.authSources[o]
			)
	return l

def getPropSources(self):
	""" Hrm I need a docstring """
	l=[]
	for o in exUserFolder.propSources.keys():
		l.append(
			exUserFolder.propSources[o]
			)
	return l

def getMembershipSources(self):
	""" Hrm I need a docstring """
	l=[]
	for o in exUserFolder.membershipSources.keys():
		l.append(
			exUserFolder.membershipSources[o]
			)
	return l

def getGroupSources(self):
	""" Hrm I need a docstring """
	return [] # UNUSED by ScoDoc: empty

def getCryptoSources(self):
	""" Doc String """
	l = []
	for o in exUserFolder.cryptoSources.keys():
		l.append(
			exUserFolder.cryptoSources[o]
			)
	return l

def MailHostIDs(self):
    """Find SQL database connections in the current folder and above

    This function return a list of ids.
    """
    return [] # UNUSED BY SCODOC

from types import ListType, IntType, LongType, FloatType, NoneType, DictType, StringType

def getVariableType(self, o):

	if type(o) == ListType:
		return 'List'
	if type(o) == IntType:
		return 'Int'
	if type(o) == LongType:
		return 'Long'
	if type(o) == FloatType:
		return 'Float'
	if type(o) == NoneType:
		return 'None'
	if type(o) == DictType:
		return 'Dict'
	if type(o) == StringType:
		return 'String'
	return 'Unknown or Restricted'

_postUserCreate='''
<dtml-comment>
Replace this method with whatever you want to do
when a user is created, you can use a Python Script,
or External Method, or keep it as a DTML Method if you
want to
</dtml-comment>
'''
