##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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
#
##############################################################################
"""SecurityInfo objects and access control constants.
SecurityInfo objects are used in class definitions to allow
a declarative style of associating access control information
with class attributes.
More information on using SecurityInfo and guide to Zope security
for developers can be found in the dev.zope.org "Declarative Security"
project:
http://dev.zope.org/Wikis/DevSite/Projects/DeclarativeSecurity
While SecurityInfo objects largely remove the need for Python
programmers to care about the underlying implementation, there
are several constants defined that should be used by code that
must set __roles__ attributes directly. (the constants are also
accessible from the AccessControl namespace). The defined access
control constants and their meanings are:
ACCESS_PUBLIC: accessible from restricted code and possibly
through the web (if object has a docstring)
ACCESS_PRIVATE: accessible only from python code
ACCESS_NONE: no access
"""
__version__='$Revision$'[11:-2]
import Acquisition, PermissionRole, sys
from zLOG import LOG,WARNING
# Security constants - these are imported into the AccessControl
# namespace and can be referenced as AccessControl.PUBLIC etc.
ACCESS_NONE = PermissionRole._what_not_even_god_should_do
ACCESS_PRIVATE = ()
ACCESS_PUBLIC = None
_marker = []
class SecurityInfo(Acquisition.Implicit):
"""Encapsulate security information."""
__security_info__ = 1
__roles__ = ACCESS_PRIVATE
def __init__(self):
self.names = {}
self.roles = {}
def _setaccess(self, names, access):
# Empty names list sets access to the class itself, named ''
if not len(names):
names = ('',)
for name in names:
if self.names.get(name, access) != access:
LOG('SecurityInfo', WARNING, 'Conflicting security '
'declarations for "%s"' % name)
self._warnings = 1
self.names[name] = access
declarePublic__roles__=ACCESS_PRIVATE
def declarePublic(self, *names):
"""Declare names to be publicly accessible."""
self._setaccess(names, ACCESS_PUBLIC)
declarePrivate__roles__=ACCESS_PRIVATE
def declarePrivate(self, *names):
"""Declare names to be inaccessible to restricted code."""
self._setaccess(names, ACCESS_PRIVATE)
declareProtected__roles__=ACCESS_PRIVATE
def declareProtected(self, permission_name, *names):
"""Declare names to be associated with a permission."""
self._setaccess(names, permission_name)
declareObjectPublic__roles__=ACCESS_PRIVATE
def declareObjectPublic(self):
"""Declare the object to be publicly accessible."""
self._setaccess((), ACCESS_PUBLIC)
declareObjectPrivate__roles__=ACCESS_PRIVATE
def declareObjectPrivate(self):
"""Declare the object to be inaccessible to restricted code."""
self._setaccess((), ACCESS_PRIVATE)
declareObjectProtected__roles__=ACCESS_PRIVATE
def declareObjectProtected(self, permission_name):
"""Declare the object to be associated with a permission."""
self._setaccess((), permission_name)
setPermissionDefault__roles__=ACCESS_PRIVATE
def setPermissionDefault(self, permission_name, roles):
"""Declare default roles for a permission"""
rdict = {}
for role in roles:
rdict[role] = 1
if self.roles.get(permission_name, rdict) != rdict:
LOG('SecurityInfo', WARNING, 'Conflicting default role'
'declarations for permission "%s"' % permission_name)
self._warnings = 1
self.roles[permission_name] = rdict
setDefaultAccess__roles__=ACCESS_PRIVATE
def setDefaultAccess(self, access):
"""Declare default attribute access policy.
This should be a boolean value, a map of attribute names to
booleans, or a callable (name, value) -> boolean.
"""
if type(access) == type(''):
access = access.lower()
if access == 'allow':
access = 1
elif access == 'deny':
access = 0
else:
raise ValueError, "'allow' or 'deny' expected"
self.access = access
class ClassSecurityInfo(SecurityInfo):
"""Encapsulate security information for class objects."""
__roles__ = ACCESS_PRIVATE
apply__roles__ = ACCESS_PRIVATE
def apply(self, classobj):
"""Apply security information to the given class object."""
dict = classobj.__dict__
# Check the class for an existing __ac_permissions__ and
# incorporate that if present to support older classes or
# classes that haven't fully switched to using SecurityInfo.
if dict.has_key('__ac_permissions__'):
for item in dict['__ac_permissions__']:
permission_name = item[0]
self._setaccess(item[1], permission_name)
if len(item) > 2:
self.setPermissionDefault(permission_name, item[2])
# Set __roles__ for attributes declared public or private.
# Collect protected attribute names in ac_permissions.
ac_permissions = {}
for name, access in self.names.items():
if access in (ACCESS_PRIVATE, ACCESS_PUBLIC, ACCESS_NONE):
dict['%s__roles__' % name] = access
else:
if not ac_permissions.has_key(access):
ac_permissions[access] = []
ac_permissions[access].append(name)
# Now transform our nested dict structure into the nested tuple
# structure expected of __ac_permissions__ attributes and set
# it on the class object.
getRoles = self.roles.get
__ac_permissions__ = []
permissions = ac_permissions.items()
permissions.sort()
for permission_name, names in permissions:
roles = getRoles(permission_name, ())
if len(roles):
entry = (permission_name, tuple(names), tuple(roles.keys()))
else:
entry = (permission_name, tuple(names))
__ac_permissions__.append(entry)
dict['__ac_permissions__'] = tuple(__ac_permissions__)
# Take care of default attribute access policy
access = getattr(self, 'access', _marker)
if access is not _marker:
dict['__allow_access_to_unprotected_subobjects__'] = access
if getattr(self, '_warnings', None):
LOG('SecurityInfo', WARNING, 'Class "%s" had conflicting '
'security declarations' % classobj.__name__)
class ClassSecurityInformation(ClassSecurityInfo):
# Default policy is disallow
access = 0
_moduleSecurity = {}
def secureModule(mname, *imp):
modsec = _moduleSecurity.get(mname, None)
if modsec is None:
return
del _moduleSecurity[mname]
if len(imp):
apply(__import__, (mname,) + tuple(imp))
module = sys.modules[mname]
modsec.apply(module.__dict__)
return module
def ModuleSecurityInfo(module_name=None):
if module_name is not None:
modsec = _moduleSecurity.get(module_name, None)
if modsec is not None:
return modsec
dot = module_name.rfind('.')
if dot > 0:
# If the module is in a package, recursively make sure
# there are security declarations for the package steps
# leading to the module
modname = module_name[dot + 1:]
pmodsec = ModuleSecurityInfo(module_name[:dot])
if not pmodsec.names.has_key(modname):
pmodsec.declarePublic(modname)
return _ModuleSecurityInfo(module_name)
class _ModuleSecurityInfo(SecurityInfo):
"""Encapsulate security information for modules."""
__roles__ = ACCESS_PRIVATE
def __init__(self, module_name=None):
self.names = {}
if module_name is not None:
global _moduleSecurity
_moduleSecurity[module_name] = self
__call____roles__ = ACCESS_PRIVATE
def __call__(self, name, value):
"""Callback for __allow_access_to_unprotected_subobjects__ hook."""
access = self.names.get(name, _marker)
if access is not _marker:
return access == ACCESS_PUBLIC
return getattr(self, 'access', 0)
apply__roles__ = ACCESS_PRIVATE
def apply(self, dict):
"""Apply security information to the given module dict."""
# Start with default attribute access policy
access = getattr(self, 'access', _marker)
if access is not _marker or len(self.names):
dict['__allow_access_to_unprotected_subobjects__'] = self
if getattr(self, '_warnings', None):
LOG('SecurityInfo', WARNING, 'Module "%s" had conflicting '
'security declarations' % dict['__name__'])
declareProtected__roles__=ACCESS_PRIVATE
def declareProtected(self, permission_name, *names):
"""Cannot declare module names protected."""
pass
declareObjectProtected__roles__=ACCESS_PRIVATE
def declareObjectProtected(self, permission_name):
"""Cannot declare module protected."""
pass
setDefaultRoles__roles__=ACCESS_PRIVATE
def setDefaultRoles(self, permission_name, roles):
"""Cannot set default roles for permissions in a module."""
pass
# Handy little utility functions
def allow_module(module_name):
"""Allow a module and all its contents to be used from a
restricted Script. The argument module_name may be a simple
or dotted module or package name. Note that if a package
path is given, all modules in the path will be available."""
ModuleSecurityInfo(module_name).setDefaultAccess(1)
dot = module_name.find('.')
while dot > 0:
ModuleSecurityInfo(module_name[:dot]).setDefaultAccess(1)
dot = module_name.find('.', dot + 1)
def allow_class(Class):
"""Allow a class and all of its methods to be used from a
restricted Script. The argument Class must be a class."""
Class._security = sec = ClassSecurityInfo()
sec.declareObjectPublic()
sec.setDefaultAccess(1)
sec.apply(Class)
from Globals import InitializeClass
InitializeClass(Class)
|