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 * ...@@ -2254,9 +2254,18 @@ static PyObject *
module_setDefaultBehaviors(PyObject *ignored, PyObject *args) module_setDefaultBehaviors(PyObject *ignored, PyObject *args)
{ {
PyObject *result = NULL; PyObject *result = NULL;
int own, auth; int own, auth, verbose;
if (PyArg_ParseTuple(args, "ii:setDefaultBehaviors", &own, &auth)) { 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; ownerous = own;
authenticated = authenticated; authenticated = authenticated;
result = Py_None; result = Py_None;
......
...@@ -61,7 +61,8 @@ class ClassSecurityInfoTests(unittest.TestCase): ...@@ -61,7 +61,8 @@ class ClassSecurityInfoTests(unittest.TestCase):
# correctly. Note that this uses carnal knowledge of the internal # correctly. Note that this uses carnal knowledge of the internal
# structures used to store this information! # structures used to store this information!
object = Test() object = Test()
imPermissionRole = object.foo__roles__ imPermissionRole = [r for r in object.foo__roles__
if not r.endswith('_Permission')]
self.failUnless(len(imPermissionRole) == 4) self.failUnless(len(imPermissionRole) == 4)
for item in ('Manager', 'Role A', 'Role B', 'Role C'): for item in ('Manager', 'Role A', 'Role B', 'Role C'):
......
...@@ -63,6 +63,11 @@ def assertPRoles(ob, permission, expect): ...@@ -63,6 +63,11 @@ def assertPRoles(ob, permission, expect):
assert roles == roles2 or tuple(roles) == tuple(roles2), ( assert roles == roles2 or tuple(roles) == tuple(roles2), (
'Different methods of checking roles computed unequal results') 'Different methods of checking roles computed unequal results')
same = 0 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 expect is None:
if (roles is None or tuple(roles) == ('Anonymous',)) and ( if (roles is None or tuple(roles) == ('Anonymous',)) and (
expect is None or tuple(expect) == ('Anonymous',)): expect is None or tuple(expect) == ('Anonymous',)):
......
...@@ -316,6 +316,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable, ...@@ -316,6 +316,28 @@ class Item(Base, Resource, CopySource, App.Management.Tabs, Traversable,
def __len__(self): def __len__(self):
return 1 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) Globals.default__class_init__(Item)
...@@ -383,27 +405,3 @@ class SimpleItem(Item, Globals.Persistent, ...@@ -383,27 +405,3 @@ class SimpleItem(Item, Globals.Persistent,
__ac_permissions__=(('View', ()),) __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 ...@@ -30,6 +30,19 @@ from Products.Five.tests.dummy import Dummy1, Dummy2
from Globals import InitializeClass 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): class PageSecurityTest(FiveTestCase):
def test_page_security(self): def test_page_security(self):
...@@ -65,7 +78,7 @@ class PageSecurityTest(FiveTestCase): ...@@ -65,7 +78,7 @@ class PageSecurityTest(FiveTestCase):
view_roles = getattr(view, '__roles__', None) view_roles = getattr(view, '__roles__', None)
self.failIf(view_roles is None) self.failIf(view_roles is None)
self.failIf(view_roles == ()) self.failIf(view_roles == ())
self.assertEquals(view_roles, ('Manager',)) assertRolesEqual(view_roles, ('Manager',))
class SecurityEquivalenceTest(FiveTestCase): class SecurityEquivalenceTest(FiveTestCase):
...@@ -109,29 +122,29 @@ class SecurityEquivalenceTest(FiveTestCase): ...@@ -109,29 +122,29 @@ class SecurityEquivalenceTest(FiveTestCase):
self.assertEquals(ac1, ac2) self.assertEquals(ac1, ac2)
bar_roles1 = getattr(self.dummy1, 'bar__roles__').__of__(self.dummy1) 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) 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__') foo_roles1 = getattr(self.dummy1, 'foo__roles__')
self.assertEquals(foo_roles1, None) assertRolesEqual(foo_roles1, None)
# XXX Not yet supported. # XXX Not yet supported.
# baz_roles1 = getattr(self.dummy1, 'baz__roles__') # baz_roles1 = getattr(self.dummy1, 'baz__roles__')
# self.assertEquals(baz_roles1, ()) # self.assertEquals(baz_roles1, ())
bar_roles2 = getattr(self.dummy2, 'bar__roles__').__of__(self.dummy2) 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) 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__') foo_roles2 = getattr(self.dummy2, 'foo__roles__')
self.assertEquals(foo_roles2, None) assertRolesEqual(foo_roles2, None)
baz_roles2 = getattr(self.dummy2, 'baz__roles__') baz_roles2 = getattr(self.dummy2, 'baz__roles__')
self.assertEquals(baz_roles2, ()) assertRolesEqual(baz_roles2, ())
class CheckPermissionTest(FiveTestCase): class CheckPermissionTest(FiveTestCase):
......
...@@ -83,10 +83,12 @@ Anonymous users are usually not allowed to create new content: ...@@ -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': Now simulate a browser request to add a 'C' instance with id 'z':
>>> request = {'id': 'z'} >>> request = {'id': 'z'}
>>> sandbox.manage_addProduct['test'].C_factory.index_html(request) >>> from zExceptions.unauthorized import Unauthorized
Traceback (most recent call last): >>> try:
... ... sandbox.manage_addProduct['test'].C_factory.index_html(request)
Unauthorized: You are not allowed to access 'C' in this context ... except Unauthorized:
... print 'not authorized'
not authorized
All right, allow the admin user to 'Add Cs': All right, allow the admin user to 'Add Cs':
......
...@@ -151,7 +151,8 @@ class ZopeStarter: ...@@ -151,7 +151,8 @@ class ZopeStarter:
self.cfg.security_policy_implementation) self.cfg.security_policy_implementation)
AccessControl.setDefaultBehaviors( AccessControl.setDefaultBehaviors(
not self.cfg.skip_ownership_checking, 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): def setupLocale(self):
# set a locale if one has been specified in the config # set a locale if one has been specified in the config
......
...@@ -621,6 +621,18 @@ ...@@ -621,6 +621,18 @@
<metadefault>off</metadefault> <metadefault>off</metadefault>
</key> </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" <key name="maximum-number-of-session-objects" datatype="integer"
default="1000" handler="maximum_number_of_session_objects"> default="1000" handler="maximum_number_of_session_objects">
<description> <description>
......
...@@ -500,12 +500,11 @@ instancehome $INSTANCE ...@@ -500,12 +500,11 @@ instancehome $INSTANCE
# Directive: security-policy-implementation # Directive: security-policy-implementation
# #
# Description: # Description:
# The default Zope security machinery is implemented in C. # The default Zope security machinery is implemented in C. Change
# Change this to "python" to use the Python version of the # this to "python" to use the Python version of the Zope security
# Zope security machinery. This impacts performance but # machinery. This setting may impact performance but is useful
# is useful for debugging purposes and required by Products such as # for debugging purposes. See also the "verbose-security" option
# VerboseSecurity, which need to "monkey-patch" the security # below.
# machinery.
# #
# Default: C # Default: C
# #
...@@ -543,6 +542,24 @@ instancehome $INSTANCE ...@@ -543,6 +542,24 @@ instancehome $INSTANCE
# skip-ownership-checking on # 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 # Directive: maximum-number-of-session-objects
# #
# Description: # 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