from paste.urlparser import StaticURLParser
from zope.component import queryMultiAdapter
from zope.component import queryUtility
from repoze.bfg.interfaces import ISecurityPolicy
from repoze.bfg.interfaces import IViewPermission
from repoze.bfg.interfaces import IView
from repoze.bfg.security import Unauthorized
from repoze.bfg.security import Allowed
from zope.interface import Interface
from repoze.bfg.interfaces import IRequest
_marker = ()
def view_execution_permitted(context, request, name=''):
""" If the view specified by ``context`` and ``name`` is protected
by a permission, return the result of checking the permission
associated with the view using the effective security policy and
the ``request``. If no security policy is in effect, or if the
view is not protected by a permission, return a True value. """
security_policy = queryUtility(ISecurityPolicy)
if security_policy:
permission = queryMultiAdapter((context, request), IViewPermission,
name=name)
if permission is None:
return Allowed(
'Allowed: view name %r in context %r (no permission '
'registered for name %r).' % (name, context, name)
)
return permission(security_policy)
return Allowed('Allowed: view name %r in context %r (no security policy '
'in use).' % (name, context))
def render_view_to_response(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request`` to an object implementing
``repoze.bfg.interfaces.IResponse`` or ``None`` if no such view
exists. This function will return ``None`` if a corresponding
view cannot be found. Additionally, this function will raise a
``ValueError`` if a view function is found and called but the view
returns an object which does not implement
``repoze.bfg.interfaces.IResponse``. If ``secure`` is ``True``,
and the view is protected by a permission, the permission will be
checked before calling the view function. If the permission check
disallows view execution (based on the current security policy), a
``repoze.bfg.security.Unauthorized`` exception will be raised; its
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
if secure:
permitted = view_execution_permitted(context, request, name)
if not permitted:
raise Unauthorized(permitted)
response = queryMultiAdapter((context, request), IView, name=name,
default=_marker)
if response is _marker:
return None
if not is_response(response):
raise ValueError('response did not implement IResponse: %r' % response)
return response
def render_view_to_iterable(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request``, and return an iterable representing
the view response's ``app_iter`` (see the interface named
``repoze.bfg.interfaces.IResponse``). This function will return
``None`` if a corresponding view cannot be found. Additionally,
this function will raise a ``ValueError`` if a view function is
found and called but the view does not return an object which
implements ``repoze.bfg.interfaces.IResponse``. You can usually
get the string representation of the return value of this function
by calling ``''.join(iterable)``, or just use ``render_view``
instead. If ``secure`` is ``True``, and the view is protected by
a permission, the permission will be checked before calling the
view function. If the permission check disallows view execution
(based on the current security policy), a
``repoze.bfg.security.Unauthorized`` exception will be raised; its
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
response = render_view_to_response(context, request, name, secure)
if response is None:
return None
return response.app_iter
def render_view(context, request, name='', secure=True):
""" Render the view named ``name`` against the specified
``context`` and ``request``, and unwind the the view response's
``app_iter`` (see the interface named
``repoze.bfg.interfaces.IResponse``) into a single string. This
function will return ``None`` if a corresponding view cannot be
found. Additionally, this function will raise a ``ValueError`` if
a view function is found and called but the view does not return
an object which implements ``repoze.bfg.interfaces.IResponse``.
If ``secure`` is ``True``, and the view is protected by a
permission, the permission will be checked before calling the view
function. If the permission check disallows view execution (based
on the current security policy), a
``repoze.bfg.security.Unauthorized`` exception will be raised; its
``args`` attribute explains why the view access was disallowed.
If ``secure`` is ``False``, no permission checking is done."""
iterable = render_view_to_iterable(context, request, name, secure)
if iterable is None:
return None
return ''.join(iterable)
def is_response(ob):
""" Return True if ``ob`` implements the
``repoze.bfg.interfaces.IResponse`` interface, False if not. Note
that this isn't actually a true Zope interface check, it's a
duck-typing check, as response objects are not obligated to
actually implement a Zope interface."""
# response objects aren't obligated to implement a Zope interface,
# so we do it the hard way
if ( hasattr(ob, 'app_iter') and hasattr(ob, 'headerlist') and
hasattr(ob, 'status') ):
if ( hasattr(ob.app_iter, '__iter__') and
hasattr(ob.headerlist, '__iter__') and
isinstance(ob.status, basestring) ) :
return True
return False
class static(object):
""" An instance of this class is a callable which can act as a BFG
view; this view will serve static files from a directory on disk
based on the ``root_dir`` you provide to its constructor. The
directory may contain subdirectories (recursively); the static
view implementation will descend into these directories as
necessary based on the components of the URL in order to resolve a
path into a response
Pass the absolute filesystem path to the directory containing
static files directory to the constructor as the ``root_dir``
argument. ``cache_max_age`` influences the Expires and Max-Age
response headers returned by the view (default is 3600 seconds or
five minutes).
"""
def __init__(self, root_dir, cache_max_age=3600):
self.app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
def __call__(self, context, request):
subpath = '/'.join(request.subpath)
request_copy = request.copy()
# Fix up PATH_INFO to get rid of everything but the "subpath"
# (the actual path to the file relative to the root dir).
request_copy.environ['PATH_INFO'] = '/' + subpath
# Zero out SCRIPT_NAME for good measure.
request_copy.environ['SCRIPT_NAME'] = ''
return request_copy.get_response(self.app)
class bfg_view(object):
""" Decorator which allows Python code to make view registrations
instead of using ZCML for the same purpose.
E.g. in the module ``views.py``::
from models import IMyModel
from repoze.bfg.interfaces import IRequest
@bfg_view(name='my_view', request_type=IRequest, for_=IMyModel,
permission='read'))
def my_view(context, request):
return render_template_to_response('templates/my.pt')
Equates to the ZCML::
If ``name`` is not supplied, the empty string is used (implying
the default view).
If ``request_type`` is not supplied, the interface
``repoze.bfg.interfaces.IRequest`` is used.
If ``for_`` is not supplied, the interface
``zope.interface.Interface`` (implying *all* interfaces) is used.
If ``permission`` is not supplied, no permission is registered for
this view (it's accessible by any caller).
Any individual or all parameters can be omitted. The simplest
bfg_view declaration then becomes::
@bfg_view()
def my_view(...):
...
Such a registration implies that the view name will be
``my_view``, registered for models with the
``zope.interface.Interface`` interface, using no permission,
registered against requests which implement the default IRequest
interface.
To make use of bfg_view declarations, insert the following
boilerplate into your application registry's ZCML::
"""
def __init__(self, name='', request_type=IRequest, for_=Interface,
permission=None):
self.name = name
self.request_type = request_type
self.for_ = for_
self.permission = permission
def __call__(self, wrapped):
# We intentionally return a do-little un-functools-wrapped
# decorator here so as to make the decorated function
# unpickleable; applications which use bfg_view decorators
# should never be able to load actions from an actions cache;
# instead they should rerun the file_configure function each
# time the application starts in case any of the decorators
# has been changed. Disallowing these functions from being
# pickled enforces that.
def decorator(context, request):
return wrapped(context, request)
decorator.__is_bfg_view__ = True
decorator.__permission__ = self.permission
decorator.__for__ = self.for_
decorator.__view_name__ = self.name
decorator.__request_type__ = self.request_type
# we assign to __grok_module__ here rather than __module__ to
# make it unpickleable but allow for the grokker to be able to
# find it
decorator.__grok_module__ = wrapped.__module__
decorator.__name__ = wrapped.__name__
decorator.__doc__ = wrapped.__doc__
decorator.__dict__.update(wrapped.__dict__)
return decorator