Source code for pyramid.security

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), )