"""Contains the classes that implement the user management subsystem
"""
from random import random

import ZODB
from persistent import Persistent
import MaKaC
import MaKaC.debug as debug
from MaKaC.common.Configuration import Config
from accessControl import AdminList
from MaKaC.common.Locators import Locator
from MaKaC.errors import UserError
from MaKaC.authentication.LocalAuthentication import LocalIdentity
from MaKaC.common.ObjectHolders import ObjectHolder, IndexHolder
from MaKaC.common import filters
import MaKaC.common.indexes as indexes
from MaKaC.errors import MaKaCError
from MaKaC.trashCan import TrashCanManager
from MaKaC.common.Configuration import Config
from MaKaC.externUsers import ExtUserHolder
import ldap
import httplib
import urllib
from xml.dom.minidom import parseString
from copy import copy

import MaKaC.debug as debug
debug.enter(__name__)

class Group(Persistent):
    """
    """
    groupType = "Default"

    def __init__(self, groupData=None):
        self.id = ""
        self.name = ""
        self.description = ""
        self.members = []

    def setId( self, newId ):
        self.id = str(newId)

    def getId( self ):
        return self.id

    def setName( self, newName ):
        self.name = newName.strip()

    def getName( self ):
        return self.name

    def setDescription( self, newDesc ):
        self.description = newDesc.strip()

    def getDescription( self ):
        return self.description

    def addMember( self, newMember ):
        if newMember == self:
            raise MaKaCError("It is not possible to add a group as member of itself")
        if self.containsMember(newMember) or newMember.containsMember(self):
            raise MaKaCError("This group already contains that member (or vice versa) or the member is None")
        self.members.append( newMember )
        if isinstance(newMember, Avatar):
            newMember.linkTo(self, "member")
        self._p_changed = 1
    
    def removeMember( self, member ):
        if member == None or member not in self.members:
            return
        self.members.remove( member )
        if isinstance(member, Avatar):
            member.unlinkTo(self, "member")
        self._p_changed = 1

    def getMemberList( self ):
        return self.members

    def containsUser( self, avatar ):
        if avatar == None:
            return 0
        for member in self.members:
            if member.containsUser( avatar ):
                return 1
        return 0

    def containsMember( self, member ):
        if member == None:
            return 0
        if member in self.members:
            return 1
        for m in self.members:
            try:
                if m.containsMember( member ):
                    return 1
            except AttributeError, e:
                continue
        return 0
    
    def canModify( self, aw ):
        return self.canUserModify( aw.getUser() )

    def canUserModify( self, user ):
        return self.containsMember(user) or \
                                (user in AdminList.getInstance().getList())

    def getLocator( self ):
        d = Locator()
        d["groupId"] = self.getId()
        return d

class NiceGroup(Group):
    
    groupType = "Nice"
    
    def addMember( self, newMember ):
        pass
    
    def removeMember( self, member ):
        pass

    def getMemberList( self ):
        return []
    
    def containsUser( self, avatar ):
        if avatar == None:
            return False
        ids = avatar.getIdentityList()
        for id in ids:
            if id.getAuthenticatorTag() == "Nice":
                return True
            
        return False
    
    def containsMember( self, member ):
        return 0

class CERNGroup(Group):
    
    groupType = "CERN"
    
    def addMember( self, newMember ):
        pass
    
    def removeMember( self, member ):
        pass
    
    def getMemberList( self ):
        return []
    
    def containsUser( self, avatar ):
        if avatar == None:
            return False
        ids = avatar.getIdentityList()
        for id in ids:
            if id.getAuthenticatorTag() == "Nice":
                params = urllib.urlencode({'UserName': id.getLogin(), 'GroupName': self.name})
                headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
                conn = httplib.HTTPSConnection("winservices-soap.web.cern.ch")
                try:
                    conn.request("POST", "/winservices-soap/CDS/Authentication.asmx/UserIsMemberOfGroup", params, headers)
                except Exception, e:
                    raise MaKaCError("Sorry, due to a temporary unavailability of the NICE service, we are unable to authenticate you. Please try later or use your local Indico account if you have one.")
                response = conn.getresponse()
                #print response.status, response.reason
                data = response.read()
                #print data
                conn.close()
                
                doc = parseString(data)
                if doc.getElementsByTagName("boolean"):
                    if doc.getElementsByTagName("boolean")[0].childNodes[0].nodeValue.encode("utf-8") == "true":
                        return True
        return False
    
    def containsMember( self, member ):
        return 0

class LDAPGroup(Group):
    
    groupType = "LDAP"
    
    def __init__(self, ldapField, filterValue):
        self._ldapField = ldapField
        self._filterValue = filterValue
    
    def addMember( self, newMember ):
        pass
    
    def removeMember( self, member ):
        pass

    def getMemberList( self ):
        return []

    def containsUser( self, avatar ):
        dict = self.getLdap("mail=%s"%avatar.getEmail())
        print dict
        if dict[self._ldapField] == self._filterValue:
            return True
        return False

    def containsMember( self, member ):
        return 0
    
    def getLdap(self, searchFilter):
        try:
            l = ldap.open("ldap.albanova.se:389")
            l.protocol_version = ldap.VERSION3    
        except ldap.LDAPError, e:
            print e
            # handle error however you like

        baseDN = "ou=people, dc=albanova, dc=se"
        searchScope = ldap.SCOPE_ONELEVEL
        ## retrieve all attributes - again adjust to your needs - see documentation for more options
        retrieveAttributes = None 
        #searchFilter = "ccid=%s"%ccid
        try:
            ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
            result_set = []
            while 1:
                result_type, result_data = l.result(ldap_result_id, 0)
                if (result_data == []):
                    break
                else:
                    ## here you don't have to append to a list
                    ## you could do whatever you want with the individual entry
                    ## The appending to list is just for illustration. 
                    if result_type == ldap.RES_SEARCH_ENTRY:
                        result_set.append(result_data)
            ret = {}
            for k in result_set[0][0][1].keys():
                ret[k] = result_set[0][0][1][k][0]
            return ret
            """
            return a dictionary. The keys are:
                'building': '',
                'division': '', 
                'group': '', 
                'cn': 'first and last name', 
                'ccid': '',
                'l': 'town', 
                'o': 'user status', 
                'pmailbox': '', 
                'telephonenumber': '', 
                'sn': 'last name', 
                'uid': '', 
                'mail': 'address email', 
                'ou': 'division group section', 
                'givenname': 'first name', 
                'section': '', 
                'homeinstitute': ''
            """
        except ldap.LDAPError, e:
            print e
        

class _GroupFFName(filters.FilterField):
    _id="name"

    def satisfies(self,group):  
        for value in self._values:
            if value.strip() != "":
                if value.strip() == "*":
                    return True
                if str(group.getName()).lower().find((str(value).strip().lower()))!=-1:
                    return True
        return False


class _GroupFilterCriteria(filters.FilterCriteria):
    _availableFields={"name":_GroupFFName}

    def __init__(self,criteria={}):
        filters.FilterCriteria.__init__(self,None,criteria)


class GroupHolder(ObjectHolder):
    """
    """
    idxName = "groups"
    counterName = "PRINCIPAL"
    
    def match(self,criteria):
        crit={}
        for f,v in criteria.items():
            crit[f]=[v]
        if crit.has_key("groupname"):
            crit["name"] = crit["groupname"]
        if "Nice" in Config.getInstance().getAuthenticatorList():
            self.updateCERNGroupMatch(crit["name"][0])
        f=filters.SimpleFilter(_GroupFilterCriteria(crit),None)
        return f.apply(self.getList())
    
    def updateCERNGroupMatch(self, name):
        params = urllib.urlencode({'pattern': name})
        headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
        try:
            conn = httplib.HTTPSConnection("winservices-soap.web.cern.ch")
            conn.request("POST", "/winservices-soap/CDS/Authentication.asmx/SearchGroups", params, headers)
            response = conn.getresponse()
            #print response.status, response.reason
            data = response.read()
            #print data
            conn.close()
        except Exception, e:
            raise MaKaCError("Sorry, due to a temporary unavailability of the NICE service, we are unable to authenticate you. Please try later or use your local Indico account if you have one.")
        
        doc = parseString(data)
        
        for elem in doc.getElementsByTagName("string"):
            name = elem.childNodes[0].nodeValue.encode("utf-8")
            if not self.hasKey(name):
                gr = CERNGroup()
                gr.setId(name)
                gr.setName(name)
                gr.setDescription("Mapping of the Nice group %s<br><br>\nMembers list: https://websvc02.cern.ch/WinServices/Services/GroupManager/GroupManager.aspx"%name)
                self.add(gr)
        


class Avatar(Persistent):
    """This class implements the representation of users inside the system. 
       Basically it contains personal data from them which is relevant for the 
       system.
    """
    linkedToBase = { "category":    {"creator":[],\
                                     "manager":[],\
                                     "access":[]},\
                     "conference":  {"creator":[],\
                                     "chair":[],\
                                     "manager":[],\
                                     "access":[],\
                                     "abstractSubmitter":[]},\
                     "session":     {"manager":[],\
                                     "access":[],\
                                     "coordinator":[]},\
                     "contribution":{"manager":[],\
                                     "submission":[],\
                                     "access":[]},\
                     "track":       {"coordinator":[]},\
                     "material":    {"access":[]},\
                     "file":        {"access":[]},\
                     "abstract":    {"submitter":[]},\
                     "registration":{"registrant":[]},\
                     "alarm":       {"to":[]},
                     "group":       {"member":[]}}

    def __init__(self, userData=None):
        """Class constructor.
           Attributes:
                userData -- dictionary containing user data to map into the 
                            avatar. Possible key values (those with * are 
                            multiple):
                                name, surname, title, organisation*, addess*,
                                email*, telephone*, fax*
        """
        self.id = ""
        self.name = ""
        self.surName = ""
        self.title = ""
        self.organisation = [""]
        self.address = [""]
        self.email = ""
        self.secondaryEmails = []
        self.telephone = [""]
        self.fax = [""]
        self.identities = []
        self.status = "Not confirmed" # The status can be 'activated', 'disabled' or 'Not confirmed'
        self.key = self.getNewKey() #key to activate the account
        self.registrants = {}
        
        self._mergeTo = None
        self._mergeFrom = []
        debug.log(self,"__init__")        
        
        self.resetLinkedTo()
        
        if userData != None:
            if userData.has_key( "name" ):
                self.setName( userData["name"] )
            if userData.has_key( "surName" ):
                self.setSurName( userData["surName"] )
            if userData.has_key( "title" ):
                self.setName( userData["title"] )
            if userData.has_key( "key" ):
                self.setKey( userData["key"] )
            if userData.has_key( "organisation" ):
                if len(userData["organisation"])>0:
                    for org in userData["organisation"]:
                        if not self.getOrganisation():
                            self.setOrganisation( org )
                        else:
                            self.addOrganisation( org )
            if userData.has_key( "address" ):
                if len(userData["address"])>0:
                    for addr in userData["address"]:
                        self.addAddress( addr )
            if userData.has_key( "email" ):
                if type(userData["email"]) == str:
                    self.setEmail(userData["email"])
                elif len(userData["email"])>0:
                    for em in userData["email"]:
                        self.setEmail( em )
            if userData.has_key( "telephone" ):
                if len(userData["telephone"])>0:
                    for tel in userData["telephone"]:
                        self.addTelephone( tel )
            if userData.has_key( "fax" ):
                if len(userData["fax"])>0:
                    for fax in userData["fax"]:
                        self.addTelephone( fax )
    
##    def __getattribute__(self, attr):
##        if object.__getattribute__(self, '_p_getattr')(attr):
##            return Persistent.__getattribute__(self, attr)
##        
##        #attributs that always get from this instance
##        if attr in ["_mergeTo", "getId", "id", "mergeTo", "_mergeFrom", "isMerged", "getMergeTo", "mergeFrom", \
##                    "unmergeFrom", "getMergeFromList"]:
##            return Persistent.__getattribute__(self,attr)
##        
##        #if _mergeTo, get attributs from the _mergeTo 
##        elif hasattr(self,"_mergeTo") and Persistent.__getattribute__(self,"_mergeTo") != None:
##            return Persistent.__getattribute__(self,"_mergeTo").__getattribute__(attr)
##        else:
##            return Persistent.__getattribute__(self,attr)
##    
##    def __setattr__(self, attr, value):
##        if self._p_setattr(attr, value):
##            return Persistent.__setattr__(self, attr, value)
##        
##        #attribute always set in this instance
##        if attr in ["_mergeTo", "id", "_mergeFrom"]:
##            Persistent.__setattr__(self, attr, value)
##        
##        elif hasattr(self,"_mergeTo") and Persistent.__getattribute__(self,"_mergeTo") != None:
##            Persistent.__getattribute__(self,"_mergeTo").__setattr__(attr, value)
##        else:
##            Persistent.__setattr__(self, attr, value)
    
    def mergeTo(self, av):
        if av:
            av.mergeFrom(self)
        if self.getMergeTo():
            self._mergeTo.unmergeFrom(self)
        self._mergeTo = av
    
    def getMergeTo(self):
        try:
            return self._mergeTo
        except:
            self._mergeTo = None
        return self._mergeTo
    
    def isMerged(self):
        if self.getMergeTo():
            return True
        return False

    def getNewKey( self ):
        length = 20
        table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
        
        key = ""
        for i in range(length):
            key += table[int(random()*len(table))]
        return key
    
    def mergeFrom(self, av):
        if not av in self.getMergeFromList():
            self._mergeFrom.append(av)
            self._p_changed = 1
    
    def unmergeFrom(self, av):
        if av in self.getMergeFromList():
            self._mergeFrom.remove(av)
            self._p_changed = 1
    
    def getMergeFromList(self):
        try:
            return self._mergeFrom
        except:
            self._mergeFrom = []
        return self._mergeFrom
    
    def getKey( self ):
        return self.key

    def setKey( self,key ):
        self.key = key

    def resetLinkedTo(self):
        self.linkedTo = copy(self.linkedToBase)
        self._p_changed = 1
    
    def setLinkedTo(self,newLinkedTo):
        self.linkedTo = newLinkedTo
        self._p_changed = 1
    
    def getLinkedTo(self):
        try:
            return self.linkedTo
        except:
            self.resetLinkedTo()
            return self.linkedTo
    
    def updateLinkedTo(self):
        self.getLinkedTo() #Create attribute if not exist
        for type in self.linkedToBase.keys():
            if type not in self.linkedTo.keys():
                self.linkedTo[type] = {}
            for role in self.linkedToBase[type].keys():
                if role not in self.linkedTo[type].keys():
                    self.linkedTo[type][role] = []
    
    def linkTo(self, obj, role):
        self.updateLinkedTo()
        if isinstance(obj, MaKaC.conference.Category):
            if not role in self.linkedTo["category"].keys():
                    raise """role "%s" not allowed for categories"""%role
            else:
                if not obj in self.linkedTo["category"][role]:
                    self.linkedTo["category"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Conference):
            if not role in self.linkedTo["conference"].keys():
                    raise """role "%s" not allowed for conferences"""%role
            else:
                if not obj in self.linkedTo["conference"][role]:
                    self.linkedTo["conference"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Session):
            if not role in self.linkedTo["session"].keys():
                    raise """role "%s" not allowed for sessions"""%role
            else:
                if not obj in self.linkedTo["session"][role]:
                    self.linkedTo["session"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Contribution):
            if not role in self.linkedTo["contribution"].keys():
                    raise """role "%s" not allowed for contributions"""%role
            else:
                if not obj in self.linkedTo["contribution"][role]:
                    self.linkedTo["contribution"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Track):
            if not role in self.linkedTo["track"].keys():
                    raise """role "%s" not allowed for tracks"""%role
            else:
                if not obj in self.linkedTo["track"][role]:
                    self.linkedTo["track"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Material):
            if not role in self.linkedTo["material"].keys():
                    raise """role "%s" not allowed for materials"""%role
            else:
                if not obj in self.linkedTo["material"][role]:
                    self.linkedTo["material"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Resource):
            if not role in self.linkedTo["resource"].keys():
                    raise """role "%s" not allowed for materials"""%role
            else:
                if not obj in self.linkedTo["resource"][role]:
                    self.linkedTo["resource"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.review.Abstract):
            if not role in self.linkedTo["abstract"].keys():
                    raise """role "%s" not allowed for abstracts"""%role
            else:
                if not obj in self.linkedTo["abstract"][role]:
                    self.linkedTo["abstract"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.registration.Registrant):
            if not role in self.linkedTo["registration"].keys():
                    raise """role "%s" not allowed for abstracts"""%role
            else:
                if not obj in self.linkedTo["registration"][role]:
                    self.linkedTo["registration"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.common.timerExec.Alarm):
            if not role in self.linkedTo["alarm"].keys():
                    raise """role "%s" not allowed for alarms"""%role
            else:
                if not obj in self.linkedTo["alarm"][role]:
                    self.linkedTo["alarm"][role].append(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.user.Group):
            if not role in self.linkedTo["group"].keys():
                    raise """role "%s" not allowed for group"""%role
            else:
                if not obj in self.linkedTo["group"][role]:
                    self.linkedTo["group"][role].append(obj)
                    self._p_changed = 1
        
    
    def unlinkTo(self, obj, role):
        self.updateLinkedTo()
        if isinstance(obj, MaKaC.conference.Category):
            if not role in self.linkedTo["category"].keys():
                    raise """role "%s" not allowed for categories"""%role
            else:
                if obj in self.linkedTo["category"][role]:
                    self.linkedTo["category"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Conference):
            if not role in self.linkedTo["conference"].keys():
                    raise """role "%s" not allowed for conferences"""%role
            else:
                if obj in self.linkedTo["conference"][role]:
                    self.linkedTo["conference"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Session):
            if not role in self.linkedTo["session"].keys():
                    raise """role "%s" not allowed for sessions"""%role
            else:
                if obj in self.linkedTo["session"][role]:
                    self.linkedTo["session"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Contribution):
            if not role in self.linkedTo["contribution"].keys():
                    raise """role "%s" not allowed for contributions"""%role
            else:
                if obj in self.linkedTo["contribution"][role]:
                    self.linkedTo["contribution"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Track):
            if not role in self.linkedTo["track"].keys():
                    raise """role "%s" not allowed for tracks"""%role
            else:
                if obj in self.linkedTo["track"][role]:
                    self.linkedTo["track"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.conference.Material):
            if not role in self.linkedTo["material"].keys():
                    raise """role "%s" not allowed for materials"""%role
            else:
                if obj in self.linkedTo["material"][role]:
                    self.linkedTo["material"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.review.Abstract):
            if not role in self.linkedTo["abstract"].keys():
                    raise """role "%s" not allowed for abstracts"""%role
            else:
                if obj in self.linkedTo["abstract"][role]:
                    self.linkedTo["abstract"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.common.timerExec.Alarm):
            if not role in self.linkedTo["alarm"].keys():
                    raise """role "%s" not allowed for alarms"""%role
            else:
                if obj in self.linkedTo["alarm"][role]:
                    self.linkedTo["alarm"][role].remove(obj)
                    self._p_changed = 1
        
        elif isinstance(obj, MaKaC.user.Group):
            if not role in self.linkedTo["group"].keys():
                    raise """role "%s" not allowed for alarms"""%role
            else:
                if obj in self.linkedTo["group"][role]:
                    self.linkedTo["group"][role].remove(obj)
                    self._p_changed = 1

    def getStatus( self ):
        try:
            return self.status
        except AttributeError:
            self.status = "activated"
            return self.status
    
    def activateAccount( self ):
        self.status = "activated"
    

    def disabledAccount( self ):
        self.status = "disabled"
    
    def isActivated( self ):
        return self.status == "activated"
    
    def isDisabled( self ):
        return self.status == "disabled"
    
    def isNotConfirmed( self ):
        return self.status == "Not confirmed"

    def setId(self, id):
        self.id = str(id)

    def getId(self):
        return self.id

    def setName(self, name):
        self.name = name
        self._p_changed = 1

    def getName(self):
        return self.name

    def getFirstName(self):
        return self.name

    def setSurName(self, name):
        self.surName = name

    def getSurName(self):
        return self.surName 

    def getFamilyName(self):
        return self.surName 

    def getFullName(self):
        surName = ""
        if self.getSurName() != "":
            surName = "%s, "%self.getSurName().upper()
        return "%s%s"%(surName, self.getName())
    
    def getAbrName(self):
        res = self.getSurName()
        if self.getName() != "":
            if res != "":
                res = "%s, "%res
            res = "%s%s."%(res, self.getName()[0].upper())
        return res

    def addOrganisation(self, newOrg):
        self.organisation.append(newOrg.strip())
        self._p_changed = 1

    def setOrganisation(self, org, item=0):
        self.organisation[item] = org.strip()
        self._p_changed = 1

    def getOrganisations(self):
        return self.organisation

    def getOrganisation( self ):
        return self.organisation[0]

    getAffiliation = getOrganisation

    def setTitle(self, title):
        self.title = title

    def getTitle( self ):
        return self.title

    def addAddress(self, newAddress):
        self.address.append(newAddress)
        self._p_changed = 1

    def getAddresses(self):
        return self.address

    def getAddress( self ):
        return self.address[0]

    def setAddress(self, address, item=0):
        self.address[item] = address
        self._p_changed = 1

    def setEmail(self, email, item=0 ):
        self.email = email.strip().lower()
    
    def getEmails( self ):
        return [self.email] + self.getSecondaryEmails()
    
    def getEmail( self ):
        return self.email
    
    def getSecondaryEmails(self):
        try:
            return self.secondaryEmails
        except:
            self.secondaryEmails = []
            return self.secondaryEmails
    
    def addSecondaryEmail(self, email):
        self.getSecondaryEmails() #create attribute if not exist
        
        if not email in self.secondaryEmails:
            self.secondaryEmails.append(email)
            self._p_changed = 1
    
    def removeSecondaryEmail(self, email):
        self.getSecondaryEmails() #create attribute if not exist
        
        if email in self.secondaryEmails:
            self.secondaryEmails.remove(email)
            self._p_changed = 1
    
    def setSecondaryEmails(self, emailList):
        self.secondaryEmails = emailList
        

    def addTelephone(self, newTel ):
        self.telephone.append( newTel )
        self._p_changed = 1

    def getTelephone( self ):
        return self.telephone[0]

    def setTelephone(self, tel, item=0 ):
        if tel != "":
            self.telephone[item] = tel
            self._p_changed = 1

    def getTelephones(self):
        return self.telephone

    def addFax(self, newFax ):
        self.fax.append( newFax )
        self._p_changed = 1

    def setFax(self, fax, item=0 ):
        self.fax[item] = fax
        self._p_changed = 1

    def getFax(self):
        return self.fax[0]

    def getFaxes(self):
        return self.fax
    
    def addIdentity(self, newId):
        if newId != None and (newId not in self.identities):
            self.identities.append( newId )
            self._p_changed = 1
    
    def removeIdentity(self, Id):
        if Id in self.identities:
            self.identities.remove(Id)
            self._p_changed = 1

    def getIdentityList( self ):
        return self.identities

    def getIdentityById(self, id, tag):
        for Id in self.identities:
            if Id.getAuthenticatorTag() == tag and Id.getLogin() == id:
                return Id
        return None

    def addRegistrant(self, n):
        if n != None and (n.getConference().getId() not in self.getRegistrants().keys()):
            self.getRegistrants()[ n.getConference().getId() ] = n
            self._p_changed = 1
    
    def removeRegistrant(self, r):
        if self.getRegistrants().has_key(r.getConference().getId()):
            del self.getRegistrants()[r.getConference().getId()]
            self._p_changed = 1

    def getRegistrantList( self ):
        return self.getRegistrants().values()

    def getRegistrants( self ):
        try:
            if self.registrants:
                pass
        except AttributeError, e:
            self.registrants = {}
            self._p_changed = 1
        return self.registrants

    def getRegistrantById(self, confId):
        if self.getRegistrants().has_key(confId):
            return self.getRegistrants()[confId]
        return None

    def isRegisteredInConf(self, conf):
        if conf.getId() in self.getRegistrants().keys():
            return True
        return False
    
    def containsUser( self, avatar ):
        return avatar == self
    containsMember = containsUser

    def canModify( self, aw ):
        return self.canUserModify( aw.getUser() )

    def canUserModify( self, user ):
        return user == self or (user in AdminList.getInstance().getList())
    
    def getLocator( self ):
        d = Locator()
        d["userId"] = self.getId()
        return d

    def delete(self):
        TrashCanManager().add(self)
    
    def recover(self):
        TrashCanManager().remove(self)


class AvatarHolder( ObjectHolder ):
    """Specialised ObjectHolder dealing with user (avatar) objects. Objects of 
       this class represent an access point to Avatars of the application and 
       provides different methods for accessing and retrieving them in several 
       ways.
    """
    idxName = "avatars"
    counterName = "PRINCIPAL"
    _indexes = [ "email", "name", "surName","organisation" ]

    def match(self, criteria, exact=0, onlyActivated=True, forceWithoutExtAuth=False):
        result = {}
        
            
        for f,v in criteria.items():
            if str(v).strip()!="" and f in self._indexes:
                match = indexes.IndexesHolder().getById(f).matchUser(v, exact=exact)
                if match!= None:
                    for userid in indexes.IndexesHolder().getById(f).matchUser(v, exact=exact):
                        if self.getById(userid) not in result:
                            av=self.getById(userid)
                            if not onlyActivated or av.isActivated():
                                if self._userMatchCriteria(av, criteria, exact):
                                    result[av.getEmail()]=av
        if not forceWithoutExtAuth:
            euh = ExtUserHolder()
            for authId in Config.getInstance().getAuthenticatorList():
                if   authId == "Nice":
                    from MaKaC.authentication import NiceAuthentication
                    auth = NiceAuthentication.NiceAuthenticator()
                elif authId == "LDAP":
                    from MaKaC.authentication import LDAPAuthentication
                    auth = LDAPAuthentication.LDAPAuthenticator()
                else:
                    continue
                debug.methodObj(self,"matching in '%s'"%authId)
                dict = euh.getById(authId).match(criteria)
                for email in dict.keys():
                    if not email in result.keys():
                        if not self.match({'email': email}, exact=1, forceWithoutExtAuth=True):
                            av = Avatar(dict[email])
                            av.setId(dict[email]["id"])
                            av.status = dict[email]["status"]
                            if self._userMatchCriteria(av, criteria, exact):
                                if auth.hasKey(dict[email]["login"]):
                                    av = auth.getById(dict[email]["login"]).getUser()
                                result[email] = av
        return result.values()
    
    def _userMatchCriteria(self, av, criteria, exact):
        if criteria.has_key("organisation"):
            if criteria["organisation"]:
##                if exact:
##                    if not criteria["organisation"].lower() in av.getOrganisations().lower():
##                        return False
##                else:
                lMatch = False
                for org in av.getOrganisations():
                    if exact:
                        if criteria["organisation"].lower() == org.lower():
                            lMatch = True
                    else:
                        if criteria["organisation"].lower() in org.lower():
                            lMatch = True
                if not lMatch:
                    return False
        
        if criteria.has_key("surName"):
            if criteria["surName"]:
                if exact:
                    if not criteria["surName"].lower() == av.getSurName().lower():
                        return False
                else:
                    if not criteria["surName"].lower() in av.getSurName().lower():
                        return False
        
        if criteria.has_key("name"):
            if criteria["name"]:
                if exact:
                    if not criteria["name"].lower() == av.getName().lower():
                        return False
                else:
                    if not criteria["name"].lower() in av.getName().lower():
                        return False
        
        if criteria.has_key("email"):
            if criteria["email"]:
    ##                if exact:
    ##                    if not criteria["email"].lower() in av.getEmails().lower():
    ##                        return False
    ##                else:
                lMatch = False
                for email in av.getEmails():
                    if exact:
                        if criteria["email"].lower() == email.lower():
                            lMatch = True
                    else:
                        if criteria["email"].lower() in email.lower():
                            lMatch = True
                if not lMatch:
                    return False
        return True
        
        
            
    
    def getById(self, id):
        try:
            return ObjectHolder.getById(self, id)
        except:
            return ObjectHolder.getById(self, id)
            pass
        try:
            authId, extId = id.split(":")
        except:
            return None
        euh = ExtUserHolder()
        dict = euh.getById(authId).getById(extId)
        av = Avatar(dict)
        identity = dict["identity"](dict["login"], av)
        dict["authenticator"].add(identity)
        av.activateAccount()
        
        #try:
        self.add(av)
        
        #except:
        #    av = self.match({'email': av.getEmail()}, exact=1, forceWithoutExtAuth=True)[0]
        return av
        

    def add(self,av):
        """
            Before adding the user, check if the email address isn't used
        """
        if av.getEmail() is None or av.getEmail()=="":
            raise UserError("User not created. You must enter an email address")
        emailmatch = self.match({'email': av.getEmail()}, exact=1, forceWithoutExtAuth=True)
        if emailmatch != None and len(emailmatch) > 0 and emailmatch[0] != '':
            raise UserError("User not created. The email address %s is already used." % av.getEmail())
        id = ObjectHolder.add(self,av)
        for i in self._indexes:
            indexes.IndexesHolder().getById(i).indexUser(av)
        return id
    
    def mergeAvatar(self, prin, merged):
        #replace merged by prin in all object where merged is
        links = merged.getLinkedTo()
        for objType in links.keys():
            if objType == "category":
                for role in links[objType].keys():
                    if role == "creator":
                        for cat in links[objType][role]:
                            cat.revokeConferenceCreation(merged)
                            cat.grantConferenceCreation(prin)
                    elif role == "manager":
                        for cat in links[objType][role]:
                            cat.revokeModification(merged)
                            cat.grantModification(prin)
                    elif role == "access":
                        for cat in links[objType][role]:
                            cat.revokeAccess(merged)
                            cat.grantAccess(prin)
                
            elif objType == "conference":
                for role in links[objType].keys():
                    if role == "creator":
                        for conf in links[objType][role]:
                            conf._setCreator(prin)
                    elif role == "chair":
                        for conf in links[objType][role]:
                            conf.removeChair(merged)
                            conf.addChair(prin)
                    elif role == "manager":
                        for conf in links[objType][role]:
                            conf.revokeModification(merged)
                            conf.grantModification(prin)
                    elif role == "access":
                        for conf in links[objType][role]:
                            conf.revokeAccess(merged)
                            conf.grantAccess(prin)
                    elif role == "abstractSubmitter":
                        for conf in links[objType][role]:
                            conf.removeAuthorizedSubmitter(merged)
                            conf.addAuthorizedSubmitter(prin)
                
            if objType == "session":
                for role in links[objType].keys():
                    if role == "manager":
                        for ses in links[objType][role]:
                            ses.revokeModification(merged)
                            ses.grantModification(prin)
                    elif role == "access":
                        for ses in links[objType][role]:
                            ses.revokeAccess(merged)
                            ses.grantAccess(prin)
                    elif role == "coordinator":
                        for ses in links[objType][role]:
                            ses.removeCoordinator(merged)
                            ses.addCoordinator(prin)
                
            if objType == "contribution":
                for role in links[objType].keys():
                    if role == "manager":
                        for contrib in links[objType][role]:
                            contrib.revokeModification(merged)
                            contrib.grantModification(prin)
                    elif role == "access":
                        for contrib in links[objType][role]:
                            contrib.revokeAccess(merged)
                            contrib.grantAccess(prin)
                    elif role == "submission":
                        for contrib in links[objType][role]:
                            contrib.revokeSubmission(merged)
                            contrib.grantSubmission(prin)
                
            if objType == "track":
                for role in links[objType].keys():
                    if role == "coordinator":
                        for track in links[objType][role]:
                            track.removeCoordinator(merged)
                            track.addCoordinator(prin)
                
            if objType == "material":
                for role in links[objType].keys():
                    if role == "access":
                        for mat in links[objType][role]:
                            mat.revokeAccess(merged)
                            mat.grantAccess(prin)
                
            if objType == "file":
                for role in links[objType].keys():
                    if role == "access":
                        for mat in links[objType][role]:
                            mat.revokeAccess(merged)
                            mat.grantAccess(prin)
                
            if objType == "abstract":
                for role in links[objType].keys():
                    if role == "submitter":
                        for abstract in links[objType][role]:
                            abstract.setSubmitter(prin)
                
            if objType == "registration":
                for role in links[objType].keys():
                    if role == "registrant":
                        for reg in links[objType][role]:
                            reg.setAvatar(prin)
                
            if objType == "alarm":
                for role in links[objType].keys():
                    if role == "to":
                        for alarm in links[objType][role]:
                            alarm.removeToUser(merged)
                            alarm.addToUser(prin)
                
            if objType == "group":
                for role in links[objType].keys():
                    if role == "member":
                        for group in links[objType][role]:
                            group.removeMember(merged)
                            group.addMember(prin)
        
        
        # remove merged from holder
        self.remove(merged)
        idxs = indexes.IndexesHolder()
        org = idxs.getById( 'organisation' )
        email = idxs.getById( 'email' )
        name = idxs.getById( 'name' )
        surName = idxs.getById( 'surName' )
        
        org.unindexUser(merged)
        email.unindexUser(merged)
        name.unindexUser(merged)
        surName.unindexUser(merged)
        
        # add merged email and logins to prin and merge users
        for mail in merged.getEmails():
            prin.addSecondaryEmail(mail)
        for id in merged.getIdentityList():
            id.setUser(prin)
            prin.addIdentity(id)
        
        merged.mergeTo(prin)
        
        # reindex prin email
        email.unindexUser(prin)
        email.indexUser(prin)
    
    def unmergeAvatar(self, prin, merged):
        if not merged in prin.getMergeFromList():
            return False
        merged.mergeTo(None)
        
        idxs = indexes.IndexesHolder()
        org = idxs.getById( 'organisation' )
        email = idxs.getById( 'email' )
        name = idxs.getById( 'name' )
        surName = idxs.getById( 'surName' )
        
        
        email.unindexUser(prin)
        for mail in merged.getEmails():
            prin.removeSecondaryEmail(mail)
        
        for id in merged.getIdentityList():
            prin.removeIdentity(id)
            id.setUser(merged)
        
        self.add(merged)
        
        org.indexUser(merged)
        email.indexUser(merged)
        name.indexUser(merged)
        surName.indexUser(merged)
        
        email.indexUser(prin)
        return True
        
        
        
        


# ToDo: This class should ideally derive from TreeHolder as it is thought to
#   be a index over the "Principal" objects i.e. it will be a top indexing of
#   the contents of AvatarHolder and GroupHolder. This will allow to 
#   transparently access to Principal objects from its id. To transparently
#   index all the objects AvatarHolder and GroupHolder must override the 
#   "add" method and, apart from their normal operation, include an adding call
#   for the PrincipalHolder.
# The problem is that I have experienced some troubles (it seems not to perform
#   the adding of objects) while adding an object both to the AvatarHolder and
#   to this one; so, for the time being, I will implement it in a "dirty" and 
#   non-optimal way to be able to continue working, but the trouble must be
#   investigated and a better solution found.
# I'll keep the ObjectHolder interface so it will be easier afterwards to 
#   implement a more optimised solution (just this object needs to be modified)
class PrincipalHolder:
    def __init__( self ):
        self.__gh = GroupHolder()
        self.__ah = AvatarHolder()
        debug.classObj("PrincipalHolder",self)
    
    def getById( self, id ):
        try:
            prin = self.__gh.getById( id )
            return prin
        except KeyError, e:
            pass
        prin = self.__ah.getById( id )
        return prin



class LoginInfo:
    
    def __init__(self, login, password):
        self.setLogin( login )
        self.setPassword( password )

    def setLogin( self, newLogin ):
        self.login = newLogin.strip()

    def getLogin( self ):
        return self.login

    def setPassword( self, newPassword ):
        self.password = newPassword

    def getPassword( self ):
        return self.password


#class AuthenticatorMgr:
#    
#    def __init__(self):
#    
#        self.AuthenticatorList = []
#        self.AuthenticatorList.append( LocalAuthenticator() )
#        #self.AuthenticatorList.append( NiceAuthenticator() )
#    
#    
#    def add( self, newId):
#        auth = self.getById( newId.getAuthenticatorTag() )
#        auth.add( newId )
#    
#    
#
#    def getById( self, id ):
#        for auth in self.AuthenticatorList:
#            if auth.getId() == id.strip():
#                return auth
#        return None
#
#    def getAvatar( self, li , authenticator=None):
#        if authenticator:
#            auth = self.getById(authenticator)
#            try:
#                return auth.getAvatar( li )
#            except KeyError, e:
#                pass
#        else:
#            
#            for auth in self.AuthenticatorList:
#                try:
#                    ret = auth.getAvatar( li )
#                    #raise "%s"%ret
#                    return ret
#                except KeyError, e:
#                    print "error:%s; auth:%s"%(e,auth)
#                    pass
#        return None
#    
#    def _getDefaultAuthenticator( self ):
#        return self.AuthenticatorList[0]    
#    
#    def getList(self):
#        return self.AuthenticatorList
#
#    def createIdentity(self, li, avatar, system=""):
#        auth = self.getById( system )
#        if not auth:
#            auth = self._getDefaultAuthenticator()
#        return auth.createIdentity(li, avatar)
#    
#    def removeIdentity( self, Id):
#        #check if there is almost another one identity
#        if len(Id.getUser().getIdentityList()) > 1:
#            auth = self.getById(Id.getAuthenticatorTag())
#            auth.remove(Id)
#    
#    def getIdentityById(self, id):
#        for auth in self.AuthenticatorList:
#            Id = auth.getById(id)
#            if Id:
#                return Id
#        return None
#    
#            
#    
#    
#class Authenthicator(ObjectHolder):
#
#    def add( self, newId ):
#        if self.hasKey( newId.getId() ):
#            raise UserError("identity already exists")
#        id = newId.getId()
#        tree = self._getIdx()
#        if tree.has_key(id):
#            raise UserError
#        tree[ id ] = newId
#        return id
#    
#    def remove(self, Id):
#        Id.getUser().removeIdentity(Id)
#        ObjectHolder.remove(self, Id)
#    
#    
#    def getById(self, id):
#        tree = self._getIdx()
#        
#        if tree.has_key( id ):
#            
#            return tree[id]
#        return None
#    
#
#    def getAvatar( self, li ):
#        #try:
#        identity = self.getById( li.getLogin() )
#        #except KeyError, e:
#        #    return None
#        ret = identity.authenticate( li )
#        #raise "%s"%ret
#        return ret
#    
#    def getIdx(self):
#        return self._getIdx()
#    
#    def getId(self):
#        return self.id
#    getId = classmethod( getId )
#    
#    def getName(self):
#        return self.name
#    
#    def getDescription(self):
#        return self.description
#
#
#class LocalAuthenticator(Authenthicator):
#    idxName = "localIdentities"
#    id = "Local"
#    name = "MaKaC"
#    desciption = "MaKaC Login"
#    
#    def createIdentity(self, li, avatar):
#        return LocalIdentity(li.getLogin(), li.getPassword(), avatar)
#    
#    
#    
#    
#class NiceAuthenticator(Authenthicator):
#    idxName = "NiceIdentities"
#    id = 'Nice'
#    name = 'NICE'
#    description = "NICE Login"
#    
#    def createIdentity(self, li, avatar):
#        return NiceIdentity( li.getLogin(), avatar )
#    
#    
#
#
#class PIdentity(Persistent):
#    
#    def __init__(self, login, user):
#        self.setLogin( login )
#        self.setUser( user )
#
#    def getId(self):
#        return self.getLogin()
#
#    def setUser(self, newUser):
#        self.user = newUser
#        newUser.addIdentity( self )
#
