Description
How to deal with permissions making your code permission aware in Plone
Permissions control whether the logged in / anonymous users can execute code paths.
Permission check is done for
The basic way of dealing with permissions is setting the permission attribute of view declaration. For more information see views.
The following code checks whether the logged in user has a certain permission for the some object.
from AccessControl import getSecurityManager
from AccessControl import Unauthorized
# Import permission names as pseudo-constants strings from somewhere... see security doc for more info
from Products.CMFCore import permissions
def some_function(self, object):
# This will
if not getSecurityManager().checkPermission(permissions.ModifyPortalContent, object):
raise Unauthorized("You need ModifyPortalContent permission to edit header animations")
# ...
# we have security clearance here
#
Example which checks if Authenticated role has a permission on a certain folder on the site using rolesOfPermission():
def checkDBPermission(self):
from zope.app.component.hooks import getSite
site = getSite()
obj = site.intranet
perms = obj.rolesOfPermission("View")
found = False
for perm in perms:
if perm["name"] == "Authenticated":
if perm["selected"] != "": # will be SELECTED if the permission is granted
found = True
break
if not found:
from Products.statusmessages.interfaces import IStatusMessage
messages = IStatusMessage(self.request)
messages.addStatusMessage(u"Possibe permission access problem with the intranet. Errors on creation form may happen.", type="info")
Object that are manageable ttw inherit of RoleManager. API class provided by this class permit you to manage permission.
Example :
>>> object.possible_permissions()
['ATContentTypes Topic: Add ATBooleanCriterion', 'ATContentTypes Topic: Add ATCurrentAuthorCriterion',...
>>> self.portal.rolesOfPermission('Modify portal content')
[{'selected': '', 'name': 'Anonymous'}, {'selected': '', 'name': 'Authenticated'}, {'selected': '', 'name': 'Contributor'}, {'selected': '', 'name': 'Editor'}, {'selected': 'SELECTED', 'name': 'GroupAdmin'}, {'selected': '', 'name': 'GroupContributor'}, {'selected': '', 'name': 'GroupEditor'}, {'selected': '', 'name': 'GroupLeader'}, {'selected': '', 'name': 'GroupMember'}, {'selected': '', 'name': 'GroupReader'}, {'selected': '', 'name': 'GroupVisitor'}, {'selected': 'SELECTED', 'name': 'Manager'}, {'selected': '', 'name': 'Member'}, {'selected': 'SELECTED', 'name': 'Owner'}, {'selected': '', 'name': 'Reader'}, {'selected': '', 'name': 'Reviewer'}, {'selected': '', 'name': 'SubscriptionViewer'}]
The current user is defined by active security manager. In both restricted and unrestricted execution certain functions may do their own security checks (invokeFactory, workflow, search) to filter out results.
If function does its own security check, there usually a code path to execute without security check. For example the methods below have security-aware and raw versions
However, in certain situations you have only security-aware code path which is blocked for the current user. You still want to execute this code path and you are sure that it does not violate your site security principles.
Below is an example how you can call any Python function and work around the security checks by establishing a temporary AccessControl.SecurityManager under special role.
Example:
from AccessControl import ClassSecurityInfo, getSecurityManager
from AccessControl.SecurityManagement import newSecurityManager, setSecurityManager
from AccessControl.User import nobody
from AccessControl.User import UnrestrictedUser as BaseUnrestrictedUser
class UnrestrictedUser(BaseUnrestrictedUser):
"""Unrestricted user that still has an id.
"""
def getId(self):
"""Return the ID of the user.
"""
return self.getUserName()
def execute_under_special_role(portal, role, function, *args, **kwargs):
""" Execute code under special role priviledges.
Example how to call::
execute_under_special_role(portal, "Manager",
doSomeNormallyNotAllowedStuff,
source_folder, target_folder)
@param portal: Reference to ISiteRoot object whose access controls we are using
@param function: Method to be called with special priviledges
@param role: User role we are using for the security context when calling the priviledged code. For example, use "Manager".
@param args: Passed to the function
@param kwargs: Passed to the function
"""
sm = getSecurityManager()
try:
try:
# Clone the current access control user and assign a new role for him/her
# Note that the username (getId()) is left in exception tracebacks in error_log
# so it is important thing to store
tmp_user = UnrestrictedUser(
sm.getUser().getId(),
'', [role],
''
)
# Act as user of the portal
tmp_user = tmp_user.__of__(portal.acl_users)
newSecurityManager(None, tmp_user)
# Call the function
return function(*args, **kwargs)
except:
# If special exception handlers are needed, run them here
raise
finally:
# Restore the old security manager
setSecurityManager(sm)
For more information, see
Gracefully failing when the user does not have a permission. Example:
from AccessControl import Unauthorized
try:
portal_state = context.restrictedTraverse("@@plone_portal_state")
except Unauthorized:
# portal_state may be limited to admin users only
portal_state = None
You don't create permissions, they "spring into existence". Whatever that means.
Permissions are usually assigned to roles, which are assigned to users through the web.
To assign a permission to a role, use profiles/default/rolemap.xml:
<?xml version="1.0"?>
<rolemap>
<permissions>
<permission name="MyProduct: MyPermission" acquire="False">
<role name="Member"/>
</permission>
</permissions>
</rolemap>
In the case you fiddle with permission and manage lock out even admin user for the content (both Plone page and raw Zope page) you can still fix the problem from debug prompt.
Example debug session how to set Access Contents Information back to all users:
>>> j=app.yoursiteid.yourfolderid.problematiccontent
>>> import AccessControl
>>> import Products.CMFCore.permissions
>>> sm=AccessControl.getSecurityManager()
>>> import transaction
>>> anon=sm.getUser()
>>> j.manage_permission(Products.CMFCore.permissions.AccessContentsInformation,roles=anon.getRoles())
>>> transaction.commit()
You can use collective.autopermission <http://pypi.python.org/pypi/collective.autopermission/1.0b1> (svn repository <http://svn.plone.org/svn/collective/collective.autopermission>) and define both the Zope 2 and Zope 3 permission at once with the <permission> zcml-directive. To do that install collective.autopermission. Either add "collective.autopermission" to "install_requires" in setup.py or to your buildout. Then include collective.autopermission's configure.zcml before you define the permissions and before you use them. (collective.autopermission is not required in Zope 2.12/Plone 4 anymore!)
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<include package="collective.autopermission" />
<permission
id="myproduct.mypermission"
title="MyProduct: MyPermission"
/>
<browser:page
for="*"
name="myexampleview"
class="browser.MyExampleView"
permission="myproduct.mypermission"
/>
</configure>
Now you can use the permission both as a Zope 2 permission ('MyProduct: MyPermission') or a Zope 3 permission ('myproduct.mypermission'). The only disadvantage is that you can't import the permissionstring as a variable from permissions.py.
If you want to protect certain actions in your product by a special permission, you most likely will want to assign this permission to a role when the product is installed. You will want to use Generic Setup's rolemap.xml to assign these permissions. A new permission will be added to the Zope instance by calling setDefaultRoles on it.
However, at the time when Generic Setup is run, almost none of your code has actually been run, so the permission doesn't exist yet. That's why we define the permissions in permissions.py, and call this from __init__.py:
# __init__.py:
import permissions
# permissions.py:
from Products.CMFCore import permissions as CMFCorePermissions
from AccessControl.SecurityInfo import ModuleSecurityInfo
from Products.CMFCore.permissions import setDefaultRoles
security = ModuleSecurityInfo('MyProduct')
security.declarePublic('MyPermission')
MyPermission = 'MyProduct: MyPermission'
setDefaultRoles(MyPermission, ())
When working with permissions, always use the variable name instead of the string value. This ensures that you can't make typos with the string value, which are hard to debug. If you do make a typo in the variable name, you'll get an ImportError or NameError.
To use your permissions with Zope 3 technologies e.g. BrowserViews/formlib/z3c.form, you need to make them available available as Zope 3 permissions. This is done in ZCML using a the <permission> directive. Example configure.zcml:
<configure
xmlns="http://namespaces.zope.org/zope">
<permission
id="myproduct.mypermission"
title="MyProduct: MyPermission"
/>
</configure>
It's convention to prefix the permission id with the nmame of the package it's defined in and use lower case only. You have to take care that the title matches exactly the permission string you used in permissions.py. Otherwise a different, zope 3 only, permission is registered.
You can use the permission to e.g. protect BrowserViews. Example configure.zcml:
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<permission
id="myproduct.mypermission"
title="MyProduct: MyPermission" />
<browser:page
for="*"
name="myexampleview"
class="browser.MyExampleView"
permission="myproduct.mypermission"
/>
</configure>
The source code of this file is hosted on GitHub. Everyone can update and fix errors in this document with few clicks - no downloads needed.
For basic information about updating this manual and Sphinx format please see Writing and updating the manual guide.