##############################################################################
#
# Copyright (c) 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
#
##############################################################################
"""Tests demonstrating consequences of guarded_getattr fix from 2004/08/07

   http://mail.zope.org/pipermail/zope-checkins/2004-August/028152.html
   http://zope.org/Collectors/CMF/259

"""

import unittest

from Testing.makerequest import makerequest

import Zope2
Zope2.startup()

from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl.Permissions import view, view_management_screens
from AccessControl.ImplPython import guarded_getattr as guarded_getattr_py
from AccessControl.ImplC import guarded_getattr as guarded_getattr_c
from Products.SiteErrorLog.SiteErrorLog import SiteErrorLog


class AllowedItem(SimpleItem):
    id = 'allowed'
    security = ClassSecurityInfo()
    security.setDefaultAccess('allow')

InitializeClass(AllowedItem)

class DeniedItem(SimpleItem):
    id = 'denied'
    security = ClassSecurityInfo()
    security.setDefaultAccess('deny')

InitializeClass(DeniedItem)

class ProtectedItem(SimpleItem):
    id = 'protected'
    security = ClassSecurityInfo()
    security.declareObjectProtected(view_management_screens)

InitializeClass(ProtectedItem)

class ProtectedSiteErrorLog(SiteErrorLog):
    '''This differs from the base by declaring security
       for the object itself.
    '''
    id = 'error_log2'
    security = ClassSecurityInfo()
    security.declareObjectProtected(view)

InitializeClass(ProtectedSiteErrorLog)


class TestGetAttr(unittest.TestCase):

    def setUp(self):
        import transaction
        self.guarded_getattr = guarded_getattr_py
        transaction.manager.begin()
        self.app = makerequest(Zope2.app())
        try:

            # Set up a manager user
            self.uf = self.app.acl_users
            self.uf._doAddUser('manager', 'secret', ['Manager'], [])
            self.login('manager')

            # Set up objects in the root that we want to aquire
            self.app.manage_addFolder('plain_folder')
            self.app._setObject('error_log2', ProtectedSiteErrorLog())

            # We also want to be able to acquire simple attributes
            self.app.manage_addProperty(id='simple_type', type='string', value='a string')

            # Set up a subfolder and the objects we want to acquire from
            self.app.manage_addFolder('subfolder')
            self.folder = self.app.subfolder
            self.folder._setObject('allowed', AllowedItem())
            self.folder._setObject('denied', DeniedItem())
            self.folder._setObject('protected', ProtectedItem())

        except:
            self.tearDown()
            raise

    def tearDown(self):
        import transaction
        noSecurityManager()
        transaction.manager.get().abort()
        self.app._p_jar.close()

    def login(self, name):
        user = self.uf.getUserById(name)
        user = user.__of__(self.uf)
        newSecurityManager(None, user)

    # Acquire plain folder

    def testFolderAllowed(self):
        o = self.guarded_getattr(self.folder.allowed, 'plain_folder')
        self.assertEqual(o, self.app.plain_folder)

    def testFolderDenied(self):
        o = self.guarded_getattr(self.folder.denied, 'plain_folder')
        self.assertEqual(o, self.app.plain_folder)

    def testFolderProtected(self):
        o = self.guarded_getattr(self.folder.protected, 'plain_folder')
        self.assertEqual(o, self.app.plain_folder)

    # Acquire user folder

    def testAclUsersAllowed(self):
        o = self.guarded_getattr(self.folder.allowed, 'acl_users')
        self.assertEqual(o, self.app.acl_users)

    def testAclUsersDenied(self):
        # XXX: Fails in 2.7.3
        o = self.guarded_getattr(self.folder.denied, 'acl_users')
        self.assertEqual(o, self.app.acl_users)

    def testAclUsersProtected(self):
        # XXX: Fails in 2.7.3 for Anonymous
        o = self.guarded_getattr(self.folder.protected, 'acl_users')
        self.assertEqual(o, self.app.acl_users)

    # Acquire browser id manager

    def testBrowserIdManagerAllowed(self):
        o = self.guarded_getattr(self.folder.allowed, 'browser_id_manager')
        self.assertEqual(o, self.app.browser_id_manager)

    def testBrowserIdManagerDenied(self):
        o = self.guarded_getattr(self.folder.denied, 'browser_id_manager')
        self.assertEqual(o, self.app.browser_id_manager)

    def testBrowserIdManagerProtected(self):
        o = self.guarded_getattr(self.folder.protected, 'browser_id_manager')
        self.assertEqual(o, self.app.browser_id_manager)

    # Acquire error log

    def testErrorLogAllowed(self):
        o = self.guarded_getattr(self.folder.allowed, 'error_log')
        self.assertEqual(o, self.app.error_log)

    def testErrorLogDenied(self):
        # XXX: Fails in 2.7.3
        o = self.guarded_getattr(self.folder.denied, 'error_log')
        self.assertEqual(o, self.app.error_log)

    def testErrorLogProtected(self):
        # XXX: Fails in 2.7.3 for Anonymous
        o = self.guarded_getattr(self.folder.protected, 'error_log')
        self.assertEqual(o, self.app.error_log)

    # Now watch this: error log with object security declaration works fine!

    def testProtectedErrorLogAllowed(self):
        o = self.guarded_getattr(self.folder.allowed, 'error_log2')
        self.assertEqual(o, self.app.error_log2)

    def testProtectedErrorLogDenied(self):
        o = self.guarded_getattr(self.folder.denied, 'error_log2')
        self.assertEqual(o, self.app.error_log2)

    def testProtectedErrorLogProtected(self):
        o = self.guarded_getattr(self.folder.protected, 'error_log2')
        self.assertEqual(o, self.app.error_log2)

    # This appears to mean that any potential acquiree must make sure
    # to declareObjectProtected(SomePermission).

    # From the ZDG:
    # We've seen how to make  assertions on methods - but in the case of
    # someObject we are not trying to access any particular method, but
    # rather the object itself (to pass it to some_method). Because the
    # security machinery will try to validate access to someObject, we
    # need a way to let the security machinery know how to handle access
    # to the object itself in addition to protecting its methods.

    # IOW, acquiring an object in restricted Python now amounts to
    # "passing it to some_method".


    # Also test Richard Jones' use-case of acquiring a string:

    def testSimpleTypeAllowed(self):
        o = self.guarded_getattr(self.folder.allowed, 'simple_type')
        self.assertEqual(o, 'a string')

    def testSimpleTypeDenied(self):
        # XXX: Fails in 2.7.3
        o = self.guarded_getattr(self.folder.denied, 'simple_type')
        self.assertEqual(o, 'a string')

    def testSimpleTypeProtected(self):
        # XXX: Fails in 2.7.3 for Anonymous
        o = self.guarded_getattr(self.folder.protected, 'simple_type')
        self.assertEqual(o, 'a string')


class TestGetAttrAnonymous(TestGetAttr):

    # Run all tests again as Anonymous User

    def setUp(self):
        TestGetAttr.setUp(self)
        # Log out
        noSecurityManager()


class TestGetAttr_c(TestGetAttr):

    def setUp(self):
        TestGetAttr.setUp(self)
        self.guarded_getattr = guarded_getattr_c

class TestGetAttrAnonymous_c(TestGetAttrAnonymous):

    def setUp(self):
        TestGetAttrAnonymous.setUp(self)
        self.guarded_getattr = guarded_getattr_c


def test_suite():
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TestGetAttr))
    suite.addTest(unittest.makeSuite(TestGetAttrAnonymous))
    suite.addTest(unittest.makeSuite(TestGetAttr_c))
    suite.addTest(unittest.makeSuite(TestGetAttrAnonymous_c))
    return suite

if __name__ == '__main__':
    unittest.main(defaultTest='test_suite')

