Commit d888bbc5 authored by Shane Hathaway's avatar Shane Hathaway

Folded in the functionality of the VerboseSecurity product.

This was also done on Zope-2_8-branch.

- Added the "verbose-security" option in zope.conf.

- Changed the Python security policy implementation to emit verbose
Unauthorized errors when verbose-security is enabled.

- Also, when verbose-security is enabled, computed roles include the
name of the permission from which the roles were derived, allowing the
security policy to reliably discover what permission is missing.

- Fixed tests that didn't pass when verbose security was enabled.

- Moved SimpleItem.__repr__ to a more basic class, where it should have
been all along.

See also:

http://mail.zope.org/pipermail/zope-dev/2005-June/025019.html
parent 9b99dbc5
This diff is collapsed.
......@@ -2254,9 +2254,18 @@ static PyObject *
module_setDefaultBehaviors(PyObject *ignored, PyObject *args)
{
PyObject *result = NULL;
int own, auth;
if (PyArg_ParseTuple(args, "ii:setDefaultBehaviors", &own, &auth)) {
int own, auth, verbose;
if (PyArg_ParseTuple(args, "iii:setDefaultBehaviors", &own, &auth,
&verbose)) {
if (verbose) {
PyErr_SetString(PyExc_NotImplementedError,
"This security policy implementation does not implement "
"the verbose option. To enable verbose security "
"exceptions, add 'security-policy-implementation "
"python' to etc/zope.conf.");
return NULL;
}
ownerous = own;
authenticated = authenticated;
result = Py_None;
......
......@@ -61,7 +61,8 @@ class ClassSecurityInfoTests(unittest.TestCase):
# correctly. Note that this uses carnal knowledge of the internal
# structures used to store this information!
object = Test()
imPermissionRole = object.foo__roles__
imPermissionRole = [r for r in object.foo__roles__
if not r.endswith('_Permission')]
self.failUnless(len(imPermissionRole) == 4)
for item in ('Manager', 'Role A', 'Role B', 'Role C'):
......
......@@ -63,6 +63,11 @@ def assertPRoles(ob, permission, expect):
assert roles == roles2 or tuple(roles) == tuple(roles2), (
'Different methods of checking roles computed unequal results')
same = 0
if roles:
# When verbose security is enabled, permission names are
# embedded in the computed roles. Remove the permission
# names.
roles = [r for r in roles if not r.endswith('_Permission')]
if roles is None or expect is None:
if (roles is None or tuple(roles) == ('Anonymous',)) and (
expect is None or tuple(expect) == ('Anonymous',)):
......
......@@ -316,6 +316,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
def __len__(self):
return 1
def __repr__(self):
"""Show the physical path of the object and its context if available.
"""
try:
path = '/'.join(self.getPhysicalPath())
except:
return Base.__repr__(self)
context_path = None
context = aq_parent(self)
container = aq_parent(aq_inner(self))
if aq_base(context) is not aq_base(container):
try:
context_path = '/'.join(context.getPhysicalPath())
except:
context_path = None
res = '<%s' % self.__class__.__name__
res += ' at %s' % path
if context_path:
res += ' used for %s' % context_path
res += '>'
return res
Globals.default__class_init__(Item)
......@@ -383,27 +405,3 @@ class SimpleItem(Item, Globals.Persistent,
__ac_permissions__=(('View', ()),)
def __repr__(self):
"""Show the physical path of the object and its context if available.
"""
try:
path = '/'.join(self.getPhysicalPath())
except:
path = None
context_path = None
context = aq_parent(self)
container = aq_parent(aq_inner(self))
if aq_base(context) is not aq_base(container):
try:
context_path = '/'.join(context.getPhysicalPath())
except:
context_path = None
res = '<%s' % self.__class__.__name__
if path:
res += ' at %s' % path
else:
res += ' at 0x%x' % id(self)
if context_path:
res += ' used for %s' % context_path
res += '>'
return res
......@@ -30,6 +30,19 @@ from Products.Five.tests.dummy import Dummy1, Dummy2
from Globals import InitializeClass
def assertRolesEqual(actual, expect):
if actual:
# filter out embedded permissions, which appear when
# verbose security is enabled
filtered = [r for r in actual if not r.endswith('_Permission')]
if isinstance(actual, tuple):
actual = tuple(filtered)
else:
actual = filtered
if actual != expect:
raise AssertionError('%s != %s' % (repr(actual), repr(expect)))
class PageSecurityTest(FiveTestCase):
def test_page_security(self):
......@@ -65,7 +78,7 @@ class PageSecurityTest(FiveTestCase):
view_roles = getattr(view, '__roles__', None)
self.failIf(view_roles is None)
self.failIf(view_roles == ())
self.assertEquals(view_roles, ('Manager',))
assertRolesEqual(view_roles, ('Manager',))
class SecurityEquivalenceTest(FiveTestCase):
......@@ -109,29 +122,29 @@ class SecurityEquivalenceTest(FiveTestCase):
self.assertEquals(ac1, ac2)
bar_roles1 = getattr(self.dummy1, 'bar__roles__').__of__(self.dummy1)
self.assertEquals(bar_roles1.__of__(self.dummy1), ('Manager',))
assertRolesEqual(bar_roles1.__of__(self.dummy1), ('Manager',))
keg_roles1 = getattr(self.dummy1, 'keg__roles__').__of__(self.dummy1)
self.assertEquals(keg_roles1.__of__(self.dummy1), ('Manager',))
assertRolesEqual(keg_roles1.__of__(self.dummy1), ('Manager',))
foo_roles1 = getattr(self.dummy1, 'foo__roles__')
self.assertEquals(foo_roles1, None)
assertRolesEqual(foo_roles1, None)
# XXX Not yet supported.
# baz_roles1 = getattr(self.dummy1, 'baz__roles__')
# self.assertEquals(baz_roles1, ())
bar_roles2 = getattr(self.dummy2, 'bar__roles__').__of__(self.dummy2)
self.assertEquals(bar_roles2.__of__(self.dummy2), ('Manager',))
assertRolesEqual(bar_roles2.__of__(self.dummy2), ('Manager',))
keg_roles2 = getattr(self.dummy2, 'keg__roles__').__of__(self.dummy2)
self.assertEquals(keg_roles2.__of__(self.dummy2), ('Manager',))
assertRolesEqual(keg_roles2.__of__(self.dummy2), ('Manager',))
foo_roles2 = getattr(self.dummy2, 'foo__roles__')
self.assertEquals(foo_roles2, None)
assertRolesEqual(foo_roles2, None)
baz_roles2 = getattr(self.dummy2, 'baz__roles__')
self.assertEquals(baz_roles2, ())
assertRolesEqual(baz_roles2, ())
class CheckPermissionTest(FiveTestCase):
......
......@@ -83,10 +83,12 @@ Anonymous users are usually not allowed to create new content:
Now simulate a browser request to add a 'C' instance with id 'z':
>>> request = {'id': 'z'}
>>> sandbox.manage_addProduct['test'].C_factory.index_html(request)
Traceback (most recent call last):
...
Unauthorized: You are not allowed to access 'C' in this context
>>> from zExceptions.unauthorized import Unauthorized
>>> try:
... sandbox.manage_addProduct['test'].C_factory.index_html(request)
... except Unauthorized:
... print 'not authorized'
not authorized
All right, allow the admin user to 'Add Cs':
......
......@@ -151,7 +151,8 @@ class ZopeStarter:
self.cfg.security_policy_implementation)
AccessControl.setDefaultBehaviors(
not self.cfg.skip_ownership_checking,
not self.cfg.skip_authentication_checking)
not self.cfg.skip_authentication_checking,
self.cfg.verbose_security)
def setupLocale(self):
# set a locale if one has been specified in the config
......
......@@ -621,6 +621,18 @@
<metadefault>off</metadefault>
</key>
<key name="verbose-security" datatype="boolean"
default="off">
<description>
Set this directive to 'on' to enable verbose security exceptions.
This can help you track down the reason for Unauthorized exceptions,
but it is not suitable for public sites because it may reveal
unnecessary information about the structure of your site. Only
works if security-policy-implementation is set to 'PYTHON'.
</description>
<metadefault>off</metadefault>
</key>
<key name="maximum-number-of-session-objects" datatype="integer"
default="1000" handler="maximum_number_of_session_objects">
<description>
......
......@@ -500,12 +500,11 @@ instancehome $INSTANCE
# Directive: security-policy-implementation
#
# Description:
# The default Zope security machinery is implemented in C.
# Change this to "python" to use the Python version of the
# Zope security machinery. This impacts performance but
# is useful for debugging purposes and required by Products such as
# VerboseSecurity, which need to "monkey-patch" the security
# machinery.
# The default Zope security machinery is implemented in C. Change
# this to "python" to use the Python version of the Zope security
# machinery. This setting may impact performance but is useful
# for debugging purposes. See also the "verbose-security" option
# below.
#
# Default: C
#
......@@ -543,6 +542,24 @@ instancehome $INSTANCE
# skip-ownership-checking on
# Directive: verbose-security
#
# Description:
# By default, Zope reports authorization failures in a terse manner in
# order to avoid revealing unnecessary information. This option
# modifies the Zope security policy to report more information about
# the reason for authorization failures. It's designed for debugging.
# If you enable this option, you must also set the
# 'security-policy-implementation' to 'python'.
#
# Default: off
#
# Example:
#
# security-policy-implementation python
# verbose-security on
# Directive: maximum-number-of-session-objects
#
# Description:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment