##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Global Authentication Utility or Principal Registry

$Id: principalregistry.py 97716 2009-03-09 19:03:27Z nadako $
"""
from zope.component import getUtility
from zope.interface import implements
from zope.container.contained import Contained, contained

import zope.security.management
from zope.security.interfaces import IGroupAwarePrincipal
from zope.password.interfaces import IPasswordManager

from zope.app.security import interfaces


class DuplicateLogin(Exception):
    pass

class DuplicateId(Exception):
    pass

class PrincipalRegistry(object):

    implements(interfaces.IAuthentication, interfaces.ILogout)

    # Methods implementing IAuthentication

    def authenticate(self, request):
        a = interfaces.ILoginPassword(request, None)
        if a is not None:
            login = a.getLogin()
            if login is not None:
                p = self.__principalsByLogin.get(login, None)
                if p is not None:
                    password = a.getPassword()
                    if p.validate(password):
                        return p
        return None

    __defaultid = None
    __defaultObject = None

    def defineDefaultPrincipal(self, id, title, description='',
                               principal=None):
        if id in self.__principalsById:
            raise DuplicateId(id)
        self.__defaultid = id
        if principal is None:
            principal = UnauthenticatedPrincipal(id, title, description)
        self.__defaultObject = contained(principal, self, id)
        return principal

    def unauthenticatedPrincipal(self):
        return self.__defaultObject

    def unauthorized(self, id, request):
        if id is None or id is self.__defaultid:
            a = interfaces.ILoginPassword(request)
            a.needLogin(realm="Zope")

    def getPrincipal(self, id):
        r = self.__principalsById.get(id)
        if r is None:
            if id == self.__defaultid:
                return self.__defaultObject
            if id == zope.security.management.system_user.id:
                return zope.security.management.system_user
            raise interfaces.PrincipalLookupError(id)
        return r

    def getPrincipalByLogin(self, login):
        return self.__principalsByLogin[login]

    def getPrincipals(self, name):
        name = name.lower()
        return [p for p in self.__principalsById.itervalues()
                  if p.title.lower().startswith(name) or
                     p.getLogin().lower().startswith(name)]

    def logout(self, request):
        # not supporting basic auth logout -- no such thing
        pass

    # Management methods

    def __init__(self):
        self.__principalsById = {}
        self.__principalsByLogin = {}

    def definePrincipal(self, principal, title, description='',
            login='', password='', passwordManagerName='Plain Text'):
        id=principal
        if login in self.__principalsByLogin:
            raise DuplicateLogin(login)

        if id in self.__principalsById or id == self.__defaultid:
            raise DuplicateId(id)

        p = Principal(id, title, description,
            login, password, passwordManagerName)
        p = contained(p, self, id)

        self.__principalsByLogin[login] = p
        self.__principalsById[id] = p

        return p

    def registerGroup(self, group):
        id = group.id
        if id in self.__principalsById or id == self.__defaultid:
            raise DuplicateId(id)

        self.__principalsById[group.id] = group

    def _clear(self):
        self.__init__()
        self.__defaultid = None
        self.__defaultObject = None

principalRegistry = PrincipalRegistry()

# Register our cleanup with Testing.CleanUp to make writing unit tests
# simpler.
try:
    from zope.testing.cleanup import addCleanUp
except ImportError:
    pass
else:
    addCleanUp(principalRegistry._clear)
    del addCleanUp

class PrincipalBase(Contained):

    def __init__(self, id, title, description):
        self.id = id
        self.title = title
        self.description = description
        self.groups = []

class Group(PrincipalBase):

    def getLogin(self):
        return '' # to make registry search happy

class Principal(PrincipalBase):

    implements(IGroupAwarePrincipal)

    def __init__(self, id, title, description, login,
            pw, pwManagerName="Plain Text"):
        super(Principal, self).__init__(id, title, description)
        self.__login = login
        self.__pwManagerName = pwManagerName
        self.__pw = pw

    def __getPasswordManager(self):
        return getUtility(IPasswordManager, self.__pwManagerName)

    def getLogin(self):
        return self.__login

    def validate(self, pw):
        pwManager = self.__getPasswordManager()
        return pwManager.checkPassword(self.__pw, pw)


class UnauthenticatedPrincipal(PrincipalBase):

    implements(interfaces.IUnauthenticatedPrincipal)


fallback_unauthenticated_principal = (
    UnauthenticatedPrincipal(
        __name__+'.fallback_unauthenticated_principal',
        'Fallback unauthenticated principal',
        'The default unauthenticated principal. Used as a fallback to '
        'allow challenging for a user even if the IAuthentication returned '
        'None as the unauthenticated principal.'))


class UnauthenticatedGroup(Group):

    implements(interfaces.IUnauthenticatedGroup)

class AuthenticatedGroup(Group):

    implements(interfaces.IAuthenticatedGroup)

class EverybodyGroup(Group):

    implements(interfaces.IEveryoneGroup)
