from zope.interface import implementer
|
|
from pyramid.interfaces import (
|
IAuthorizationPolicy,
|
IAuthenticationPolicy,
|
ICSRFStoragePolicy,
|
IDefaultCSRFOptions,
|
IDefaultPermission,
|
PHASE1_CONFIG,
|
PHASE2_CONFIG,
|
)
|
|
from pyramid.csrf import LegacySessionCSRFStoragePolicy
|
from pyramid.exceptions import ConfigurationError
|
from pyramid.util import as_sorted_tuple
|
|
from pyramid.config.actions import action_method
|
|
|
class SecurityConfiguratorMixin(object):
|
def add_default_security(self):
|
self.set_csrf_storage_policy(LegacySessionCSRFStoragePolicy())
|
|
@action_method
|
def set_authentication_policy(self, policy):
|
""" Override the :app:`Pyramid` :term:`authentication policy` in the
|
current configuration. The ``policy`` argument must be an instance
|
of an authentication policy or a :term:`dotted Python name`
|
that points at an instance of an authentication policy.
|
|
.. note::
|
|
Using the ``authentication_policy`` argument to the
|
:class:`pyramid.config.Configurator` constructor can be used to
|
achieve the same purpose.
|
|
"""
|
|
def register():
|
self._set_authentication_policy(policy)
|
if self.registry.queryUtility(IAuthorizationPolicy) is None:
|
raise ConfigurationError(
|
'Cannot configure an authentication policy without '
|
'also configuring an authorization policy '
|
'(use the set_authorization_policy method)'
|
)
|
|
intr = self.introspectable(
|
'authentication policy',
|
None,
|
self.object_description(policy),
|
'authentication policy',
|
)
|
intr['policy'] = policy
|
# authentication policy used by view config (phase 3)
|
self.action(
|
IAuthenticationPolicy,
|
register,
|
order=PHASE2_CONFIG,
|
introspectables=(intr,),
|
)
|
|
def _set_authentication_policy(self, policy):
|
policy = self.maybe_dotted(policy)
|
self.registry.registerUtility(policy, IAuthenticationPolicy)
|
|
@action_method
|
def set_authorization_policy(self, policy):
|
""" Override the :app:`Pyramid` :term:`authorization policy` in the
|
current configuration. The ``policy`` argument must be an instance
|
of an authorization policy or a :term:`dotted Python name` that points
|
at an instance of an authorization policy.
|
|
.. note::
|
|
Using the ``authorization_policy`` argument to the
|
:class:`pyramid.config.Configurator` constructor can be used to
|
achieve the same purpose.
|
"""
|
|
def register():
|
self._set_authorization_policy(policy)
|
|
def ensure():
|
if self.autocommit:
|
return
|
if self.registry.queryUtility(IAuthenticationPolicy) is None:
|
raise ConfigurationError(
|
'Cannot configure an authorization policy without '
|
'also configuring an authentication policy '
|
'(use the set_authorization_policy method)'
|
)
|
|
intr = self.introspectable(
|
'authorization policy',
|
None,
|
self.object_description(policy),
|
'authorization policy',
|
)
|
intr['policy'] = policy
|
# authorization policy used by view config (phase 3) and
|
# authentication policy (phase 2)
|
self.action(
|
IAuthorizationPolicy,
|
register,
|
order=PHASE1_CONFIG,
|
introspectables=(intr,),
|
)
|
self.action(None, ensure)
|
|
def _set_authorization_policy(self, policy):
|
policy = self.maybe_dotted(policy)
|
self.registry.registerUtility(policy, IAuthorizationPolicy)
|
|
@action_method
|
def set_default_permission(self, permission):
|
"""
|
Set the default permission to be used by all subsequent
|
:term:`view configuration` registrations. ``permission``
|
should be a :term:`permission` string to be used as the
|
default permission. An example of a permission
|
string:``'view'``. Adding a default permission makes it
|
unnecessary to protect each view configuration with an
|
explicit permission, unless your application policy requires
|
some exception for a particular view.
|
|
If a default permission is *not* set, views represented by
|
view configuration registrations which do not explicitly
|
declare a permission will be executable by entirely anonymous
|
users (any authorization policy is ignored).
|
|
Later calls to this method override will conflict with earlier calls;
|
there can be only one default permission active at a time within an
|
application.
|
|
.. warning::
|
|
If a default permission is in effect, view configurations meant to
|
create a truly anonymously accessible view (even :term:`exception
|
view` views) *must* use the value of the permission importable as
|
:data:`pyramid.security.NO_PERMISSION_REQUIRED`. When this string
|
is used as the ``permission`` for a view configuration, the default
|
permission is ignored, and the view is registered, making it
|
available to all callers regardless of their credentials.
|
|
.. seealso::
|
|
See also :ref:`setting_a_default_permission`.
|
|
.. note::
|
|
Using the ``default_permission`` argument to the
|
:class:`pyramid.config.Configurator` constructor can be used to
|
achieve the same purpose.
|
"""
|
|
def register():
|
self.registry.registerUtility(permission, IDefaultPermission)
|
|
intr = self.introspectable(
|
'default permission', None, permission, 'default permission'
|
)
|
intr['value'] = permission
|
perm_intr = self.introspectable(
|
'permissions', permission, permission, 'permission'
|
)
|
perm_intr['value'] = permission
|
# default permission used during view registration (phase 3)
|
self.action(
|
IDefaultPermission,
|
register,
|
order=PHASE1_CONFIG,
|
introspectables=(intr, perm_intr),
|
)
|
|
def add_permission(self, permission_name):
|
"""
|
A configurator directive which registers a free-standing
|
permission without associating it with a view callable. This can be
|
used so that the permission shows up in the introspectable data under
|
the ``permissions`` category (permissions mentioned via ``add_view``
|
already end up in there). For example::
|
|
config = Configurator()
|
config.add_permission('view')
|
"""
|
intr = self.introspectable(
|
'permissions', permission_name, permission_name, 'permission'
|
)
|
intr['value'] = permission_name
|
self.action(None, introspectables=(intr,))
|
|
@action_method
|
def set_default_csrf_options(
|
self,
|
require_csrf=True,
|
token='csrf_token',
|
header='X-CSRF-Token',
|
safe_methods=('GET', 'HEAD', 'OPTIONS', 'TRACE'),
|
callback=None,
|
):
|
"""
|
Set the default CSRF options used by subsequent view registrations.
|
|
``require_csrf`` controls whether CSRF checks will be automatically
|
enabled on each view in the application. This value is used as the
|
fallback when ``require_csrf`` is left at the default of ``None`` on
|
:meth:`pyramid.config.Configurator.add_view`.
|
|
``token`` is the name of the CSRF token used in the body of the
|
request, accessed via ``request.POST[token]``. Default: ``csrf_token``.
|
|
``header`` is the name of the header containing the CSRF token,
|
accessed via ``request.headers[header]``. Default: ``X-CSRF-Token``.
|
|
If ``token`` or ``header`` are set to ``None`` they will not be used
|
for checking CSRF tokens.
|
|
``safe_methods`` is an iterable of HTTP methods which are expected to
|
not contain side-effects as defined by RFC2616. Safe methods will
|
never be automatically checked for CSRF tokens.
|
Default: ``('GET', 'HEAD', 'OPTIONS', TRACE')``.
|
|
If ``callback`` is set, it must be a callable accepting ``(request)``
|
and returning ``True`` if the request should be checked for a valid
|
CSRF token. This callback allows an application to support
|
alternate authentication methods that do not rely on cookies which
|
are not subject to CSRF attacks. For example, if a request is
|
authenticated using the ``Authorization`` header instead of a cookie,
|
this may return ``False`` for that request so that clients do not
|
need to send the ``X-CSRF-Token`` header. The callback is only tested
|
for non-safe methods as defined by ``safe_methods``.
|
|
.. versionadded:: 1.7
|
|
.. versionchanged:: 1.8
|
Added the ``callback`` option.
|
|
"""
|
options = DefaultCSRFOptions(
|
require_csrf, token, header, safe_methods, callback
|
)
|
|
def register():
|
self.registry.registerUtility(options, IDefaultCSRFOptions)
|
|
intr = self.introspectable(
|
'default csrf view options',
|
None,
|
options,
|
'default csrf view options',
|
)
|
intr['require_csrf'] = require_csrf
|
intr['token'] = token
|
intr['header'] = header
|
intr['safe_methods'] = as_sorted_tuple(safe_methods)
|
intr['callback'] = callback
|
|
self.action(
|
IDefaultCSRFOptions,
|
register,
|
order=PHASE1_CONFIG,
|
introspectables=(intr,),
|
)
|
|
@action_method
|
def set_csrf_storage_policy(self, policy):
|
"""
|
Set the :term:`CSRF storage policy` used by subsequent view
|
registrations.
|
|
``policy`` is a class that implements the
|
:meth:`pyramid.interfaces.ICSRFStoragePolicy` interface and defines
|
how to generate and persist CSRF tokens.
|
|
"""
|
|
def register():
|
self.registry.registerUtility(policy, ICSRFStoragePolicy)
|
|
intr = self.introspectable(
|
'csrf storage policy', None, policy, 'csrf storage policy'
|
)
|
intr['policy'] = policy
|
self.action(ICSRFStoragePolicy, register, introspectables=(intr,))
|
|
|
@implementer(IDefaultCSRFOptions)
|
class DefaultCSRFOptions(object):
|
def __init__(self, require_csrf, token, header, safe_methods, callback):
|
self.require_csrf = require_csrf
|
self.token = token
|
self.header = header
|
self.safe_methods = frozenset(safe_methods)
|
self.callback = callback
|