from zope.deprecation import deprecated
from zope.interface import implementer, providedBy
from pyramid.interfaces import (
IAuthenticationPolicy,
IAuthorizationPolicy,
ISecuredView,
ISecurityPolicy,
IView,
IViewClassifier,
)
from pyramid.threadlocal import get_current_registry
NO_PERMISSION_REQUIRED = '__no_permission_required__'
def _get_security_policy(request):
return request.registry.queryUtility(ISecurityPolicy)
[docs]
def remember(request, userid, **kw):
"""
Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``)
on this request's response.
These headers are suitable for 'remembering' a set of credentials
implied by the data passed as ``userid`` and ``*kw`` using the
current :term:`security policy`. Common usage might look
like so within the body of a view function (``response`` is
assumed to be a :term:`WebOb` -style :term:`response` object
computed previously by the view code):
.. code-block:: python
from pyramid.security import remember
headers = remember(request, 'chrism', password='123', max_age='86400')
response = request.response
response.headerlist.extend(headers)
return response
If no :term:`security policy` is in use, this function will
always return an empty sequence. If used, the composition and
meaning of ``**kw`` must be agreed upon by the calling code and
the effective security policy.
.. versionchanged:: 1.6
Deprecated the ``principal`` argument in favor of ``userid`` to clarify
its relationship to the security policy.
.. versionchanged:: 1.10
Removed the deprecated ``principal`` argument.
"""
policy = _get_security_policy(request)
if policy is None:
return []
return policy.remember(request, userid, **kw)
[docs]
def forget(request, **kw):
"""
Return a sequence of header tuples (e.g. ``[('Set-Cookie',
'foo=abc')]``) suitable for 'forgetting' the set of credentials
possessed by the currently authenticated user. A common usage
might look like so within the body of a view function
(``response`` is assumed to be an :term:`WebOb` -style
:term:`response` object computed previously by the view code):
.. code-block:: python
from pyramid.security import forget
headers = forget(request)
response.headerlist.extend(headers)
return response
If no :term:`security policy` is in use, this function will
always return an empty sequence.
"""
policy = _get_security_policy(request)
if policy is None:
return []
return policy.forget(request, **kw)
[docs]
def principals_allowed_by_permission(context, permission):
"""
.. deprecated:: 2.0
The new security policy has removed the concept of principals. See
:ref:`upgrading_auth_20` for more information.
Provided a ``context`` (a resource object), and a ``permission``
string, if an :term:`authorization policy` is
in effect, return a sequence of :term:`principal` ids that possess
the permission in the ``context``. If no authorization policy is
in effect, this will return a sequence with the single value
:mod:`pyramid.authorization.Everyone` (the special principal
identifier representing all principals).
.. note::
Even if an :term:`authorization policy` is in effect,
some (exotic) authorization policies may not implement the
required machinery for this function; those will cause a
:exc:`NotImplementedError` exception to be raised when this
function is invoked.
"""
reg = get_current_registry()
policy = reg.queryUtility(IAuthorizationPolicy)
if policy is None:
from pyramid.authorization import Everyone # noqa: F811
return [Everyone]
return policy.principals_allowed_by_permission(context, permission)
deprecated(
'principals_allowed_by_permission',
'The new security policy has removed the concept of principals. See '
'"Upgrading Authentication/Authorization" in "What\'s New in Pyramid 2.0" '
'of the documentation for more information.',
)
[docs]
def view_execution_permitted(context, request, name=''):
"""If the view specified by ``context`` and ``name`` is protected
by a :term:`permission`, check the permission associated with the
view using the effective authentication/authorization policies and
the ``request``. Return a boolean result. If no
:term:`security policy` is in effect, or if the view is not
protected by a permission, return ``True``. If no view can view found,
an exception will be raised.
.. versionchanged:: 1.4a4
An exception is raised if no view is found.
"""
reg = request.registry
provides = [IViewClassifier] + [providedBy(x) for x in (request, context)]
# XXX not sure what to do here about using _find_views or analogue;
# for now let's just keep it as-is
view = reg.adapters.lookup(provides, ISecuredView, name=name)
if view is None:
view = reg.adapters.lookup(provides, IView, name=name)
if view is None:
raise TypeError(
'No registered view satisfies the constraints. '
'It would not make sense to claim that this view '
'"is" or "is not" permitted.'
)
return Allowed(
'Allowed: view name %r in context %r (no permission defined)'
% (name, context)
)
return view.__permitted__(context, request)
class PermitsResult(int):
def __new__(cls, s, *args):
"""
Create a new instance.
:param fmt: A format string explaining the reason for denial.
:param args: Arguments are stored and used with the format string
to generate the ``msg``.
"""
inst = int.__new__(cls, cls.boolval)
inst.s = s
inst.args = args
return inst
@property
def msg(self):
"""A string indicating why the result was generated."""
return self.s % self.args
def __str__(self):
return self.msg
def __repr__(self):
return '<%s instance at %s with msg %r>' % (
self.__class__.__name__,
id(self),
self.msg,
)
[docs]
class Denied(PermitsResult):
"""
An instance of ``Denied`` is returned when a security-related
API or other :app:`Pyramid` code denies an action unrelated to
an ACL check. It evaluates equal to all boolean false types. It
has an attribute named ``msg`` describing the circumstances for
the deny.
"""
boolval = 0
[docs]
class Allowed(PermitsResult):
"""
An instance of ``Allowed`` is returned when a security-related
API or other :app:`Pyramid` code allows an action unrelated to
an ACL check. It evaluates equal to all boolean true types. It
has an attribute named ``msg`` describing the circumstances for
the allow.
"""
boolval = 1
class SecurityAPIMixin:
"""Mixin for Request class providing auth-related properties."""
@property
def identity(self):
"""
Return an opaque object identifying the current user or ``None`` if no
user is authenticated or there is no :term:`security policy` in effect.
"""
policy = _get_security_policy(self)
if policy is None:
return None
return policy.identity(self)
@property
def authenticated_userid(self):
"""
Return the :term:`userid` of the currently authenticated user or
``None`` if there is no :term:`security policy` in effect or there is
no currently authenticated user.
.. versionchanged:: 2.0
This property delegates to the effective :term:`security policy`,
ignoring old-style :term:`authentication policy`.
"""
policy = _get_security_policy(self)
if policy is None:
return None
return policy.authenticated_userid(self)
@property
def is_authenticated(self):
"""Return ``True`` if a user is authenticated for this request."""
return self.authenticated_userid is not None
def has_permission(self, permission, context=None):
"""Given a permission and an optional context, returns an instance of
:data:`pyramid.security.Allowed` if the permission is granted to this
request with the provided context, or the context already associated
with the request. Otherwise, returns an instance of
:data:`pyramid.security.Denied`. This method delegates to the current
security policy. Returns
:data:`pyramid.security.Allowed` unconditionally if no security
policy has been registered for this request. If ``context`` is not
supplied or is supplied as ``None``, the context used is the
``request.context`` attribute.
:param permission: Does this request have the given permission?
:type permission: str
:param context: A resource object or ``None``
:type context: object
:returns: Either :class:`pyramid.security.Allowed` or
:class:`pyramid.security.Denied`.
"""
if context is None:
context = self.context
policy = _get_security_policy(self)
if policy is None:
return Allowed('No security policy in use.')
return policy.permits(self, context, permission)
class AuthenticationAPIMixin:
"""Mixin for Request class providing compatibility properties."""
@property
def unauthenticated_userid(self):
"""
.. deprecated:: 2.0
``unauthenticated_userid`` does not have an equivalent in the new
security system. Use :attr:`.authenticated_userid` or
:attr:`.identity` instead. See :ref:`upgrading_auth_20` for more
information.
Return an object which represents the *claimed* (not verified) user
id of the credentials present in the request. ``None`` if there is no
:term:`authentication policy` in effect or there is no user data
associated with the current request. This differs from
:attr:`~pyramid.request.Request.authenticated_userid`, because the
effective authentication policy will not ensure that a record
associated with the userid exists in persistent storage.
"""
security = _get_security_policy(self)
if security is None:
return None
if isinstance(security, LegacySecurityPolicy):
authn = security._get_authn_policy(self)
return authn.unauthenticated_userid(self)
return security.authenticated_userid(self)
unauthenticated_userid = deprecated(
unauthenticated_userid,
(
'The new security policy has deprecated unauthenticated_userid. '
'See "Upgrading Authentication/Authorization" in "What\'s New in '
'Pyramid 2.0" of the documentation for more information.'
),
)
@property
def effective_principals(self):
"""
.. deprecated:: 2.0
The new security policy has removed the concept of principals. See
:ref:`upgrading_auth_20` for more information.
Return the list of 'effective' :term:`principal` identifiers
for the ``request``. If no :term:`authentication policy` is in effect,
this will return a one-element list containing the
:data:`pyramid.authorization.Everyone` principal.
"""
from pyramid.authorization import Everyone # noqa: F811
security = _get_security_policy(self)
if security is not None and isinstance(security, LegacySecurityPolicy):
authn = security._get_authn_policy(self)
return authn.effective_principals(self)
return [Everyone]
effective_principals = deprecated(
effective_principals,
(
'The new security policy has deprecated effective_principals. '
'See "Upgrading Authentication/Authorization" in "What\'s New in '
'Pyramid 2.0" of the documentation for more information.'
),
)
@implementer(ISecurityPolicy)
class LegacySecurityPolicy:
"""
A :term:`security policy` which provides a backwards compatibility shim for
the :term:`authentication policy` and the :term:`authorization policy`.
"""
def _get_authn_policy(self, request):
return request.registry.getUtility(IAuthenticationPolicy)
def _get_authz_policy(self, request):
return request.registry.getUtility(IAuthorizationPolicy)
def identity(self, request):
return self.authenticated_userid(request)
def authenticated_userid(self, request):
authn = self._get_authn_policy(request)
return authn.authenticated_userid(request)
def remember(self, request, userid, **kw):
authn = self._get_authn_policy(request)
return authn.remember(request, userid, **kw)
def forget(self, request, **kw):
if kw:
raise ValueError(
'Legacy authentication policies do not support keyword '
'arguments for `forget`'
)
authn = self._get_authn_policy(request)
return authn.forget(request)
def permits(self, request, context, permission):
authn = self._get_authn_policy(request)
authz = self._get_authz_policy(request)
principals = authn.effective_principals(request)
return authz.permits(context, principals, permission)
Everyone = 'system.Everyone'
Authenticated = 'system.Authenticated'
Allow = 'Allow'
Deny = 'Deny'
class AllPermissionsList:
"""Stand in 'permission list' to represent all permissions"""
def __iter__(self):
return iter(())
def __contains__(self, other):
return True
def __eq__(self, other):
return isinstance(other, self.__class__)
ALL_PERMISSIONS = AllPermissionsList()
DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS)
class ACLPermitsResult(PermitsResult):
def __new__(cls, ace, acl, permission, principals, context):
"""
Create a new instance.
:param ace: The :term:`ACE` that matched, triggering the result.
:param acl: The :term:`ACL` containing ``ace``.
:param permission: The required :term:`permission`.
:param principals: The list of :term:`principals <principal>` provided.
:param context: The :term:`context` providing the :term:`lineage`
searched.
"""
fmt = (
'%s permission %r via ACE %r in ACL %r on context %r for '
'principals %r'
)
inst = PermitsResult.__new__(
cls, fmt, cls.__name__, permission, ace, acl, context, principals
)
inst.permission = permission
inst.ace = ace
inst.acl = acl
inst.principals = principals
inst.context = context
return inst
[docs]
class ACLDenied(ACLPermitsResult, Denied):
"""
An instance of ``ACLDenied`` is a specialization of
:class:`pyramid.security.Denied` that represents that a security check
made explicitly against ACL was denied. It evaluates equal to all
boolean false types. It also has the following attributes: ``acl``,
``ace``, ``permission``, ``principals``, and ``context``. These
attributes indicate the security values involved in the request. Its
``__str__`` method prints a summary of these attributes for debugging
purposes. The same summary is available as the ``msg`` attribute.
"""
[docs]
class ACLAllowed(ACLPermitsResult, Allowed):
"""
An instance of ``ACLAllowed`` is a specialization of
:class:`pyramid.security.Allowed` that represents that a security check
made explicitly against ACL was allowed. It evaluates equal to all
boolean true types. It also has the following attributes: ``acl``,
``ace``, ``permission``, ``principals``, and ``context``. These
attributes indicate the security values involved in the request. Its
``__str__`` method prints a summary of these attributes for debugging
purposes. The same summary is available as the ``msg`` attribute.
"""
for attr in (
'ALL_PERMISSIONS',
'DENY_ALL',
'ACLAllowed',
'ACLDenied',
'AllPermissionsList',
'Allow',
'Authenticated',
'Deny',
'Everyone',
):
deprecated(
attr,
'"pyramid.security.{attr}" is deprecated in Pyramid 2.0. Adjust your '
'import to "pyramid.authorization.{attr}"'.format(attr=attr),
)