Michael Merickel
2018-10-18 66a767f0e1911543b77a4dd768821ee2ed40390a
commit | author | age
c151ad 1 from zope.deprecation import deprecated
6b6e43 2 from zope.interface import providedBy
4ac0ff 3
0c1c39 4 from pyramid.interfaces import (
CM 5     IAuthenticationPolicy,
6     IAuthorizationPolicy,
7     ISecuredView,
4b552e 8     IView,
0c1c39 9     IViewClassifier,
0c29cf 10 )
d75fe7 11
475532 12 from pyramid.compat import map_
b60bdb 13 from pyramid.threadlocal import get_current_registry
2466f6 14
CM 15 Everyone = 'system.Everyone'
16 Authenticated = 'system.Authenticated'
17 Allow = 'Allow'
18 Deny = 'Deny'
0c29cf 19
226b49 20
CM 21 class AllPermissionsList(object):
22     """ Stand in 'permission list' to represent all permissions """
1814cd 23
226b49 24     def __iter__(self):
1814cd 25         return iter(())
TS 26
226b49 27     def __contains__(self, other):
CM 28         return True
1814cd 29
226b49 30     def __eq__(self, other):
CM 31         return isinstance(other, self.__class__)
32
0c29cf 33
226b49 34 ALL_PERMISSIONS = AllPermissionsList()
CM 35 DENY_ALL = (Deny, Everyone, ALL_PERMISSIONS)
2466f6 36
feceff 37 NO_PERMISSION_REQUIRED = '__no_permission_required__'
MM 38
0c29cf 39
3c2f95 40 def _get_registry(request):
MR 41     try:
42         reg = request.registry
43     except AttributeError:
0c29cf 44         reg = get_current_registry()  # b/c
3c2f95 45     return reg
0c29cf 46
3c2f95 47
0dcd56 48 def _get_authentication_policy(request):
CM 49     registry = _get_registry(request)
50     return registry.queryUtility(IAuthenticationPolicy)
0c29cf 51
0dcd56 52
4ac0ff 53 def has_permission(permission, context, request):
0184b5 54     """
2033ee 55     A function that calls :meth:`pyramid.request.Request.has_permission`
SP 56     and returns its result.
a54bc1 57
0184b5 58     .. deprecated:: 1.5
2033ee 59         Use :meth:`pyramid.request.Request.has_permission` instead.
4ac0ff 60
0184b5 61     .. versionchanged:: 1.5a3
2033ee 62         If context is None, then attempt to use the context attribute of self;
SP 63         if not set, then the AttributeError is propagated.
0c29cf 64     """
3c2f95 65     return request.has_permission(permission, context)
0c29cf 66
a1a9fb 67
0184b5 68 deprecated(
CM 69     'has_permission',
70     'As of Pyramid 1.5 the "pyramid.security.has_permission" API is now '
71ad60 71     'deprecated.  It will be removed in Pyramid 1.8.  Use the '
0c29cf 72     '"has_permission" method of the Pyramid request instead.',
MM 73 )
a1a9fb 74
0184b5 75
CM 76 def authenticated_userid(request):
77     """
78     A function that returns the value of the property
79     :attr:`pyramid.request.Request.authenticated_userid`.
a54bc1 80
0184b5 81     .. deprecated:: 1.5
CM 82        Use :attr:`pyramid.request.Request.authenticated_userid` instead.
0c29cf 83     """
3c2f95 84     return request.authenticated_userid
0c29cf 85
b54cdb 86
0184b5 87 deprecated(
CM 88     'authenticated_userid',
89     'As of Pyramid 1.5 the "pyramid.security.authenticated_userid" API is now '
71ad60 90     'deprecated.  It will be removed in Pyramid 1.8.  Use the '
0c29cf 91     '"authenticated_userid" attribute of the Pyramid request instead.',
MM 92 )
93
2526d8 94
0184b5 95 def unauthenticated_userid(request):
a54bc1 96     """
0184b5 97     A function that returns the value of the property
CM 98     :attr:`pyramid.request.Request.unauthenticated_userid`.
a54bc1 99
0184b5 100     .. deprecated:: 1.5
2033ee 101         Use :attr:`pyramid.request.Request.unauthenticated_userid` instead.
0c29cf 102     """
3c2f95 103     return request.unauthenticated_userid
0c29cf 104
2526d8 105
0184b5 106 deprecated(
CM 107     'unauthenticated_userid',
108     'As of Pyramid 1.5 the "pyramid.security.unauthenticated_userid" API is '
71ad60 109     'now deprecated.  It will be removed in Pyramid 1.8.  Use the '
0c29cf 110     '"unauthenticated_userid" attribute of the Pyramid request instead.',
MM 111 )
112
a1a9fb 113
0184b5 114 def effective_principals(request):
CM 115     """
116     A function that returns the value of the property
117     :attr:`pyramid.request.Request.effective_principals`.
a54bc1 118
0184b5 119     .. deprecated:: 1.5
2033ee 120         Use :attr:`pyramid.request.Request.effective_principals` instead.
0c29cf 121     """
3c2f95 122     return request.effective_principals
0c29cf 123
3c2f95 124
0184b5 125 deprecated(
CM 126     'effective_principals',
127     'As of Pyramid 1.5 the "pyramid.security.effective_principals" API is '
71ad60 128     'now deprecated.  It will be removed in Pyramid 1.8.  Use the '
0c29cf 129     '"effective_principals" attribute of the Pyramid request instead.',
MM 130 )
131
3c2f95 132
781371 133 def remember(request, userid, **kw):
0184b5 134     """
0dcd56 135     Returns a sequence of header tuples (e.g. ``[('Set-Cookie', 'foo=abc')]``)
CM 136     on this request's response.
0184b5 137     These headers are suitable for 'remembering' a set of credentials
c7afe4 138     implied by the data passed as ``userid`` and ``*kw`` using the
0184b5 139     current :term:`authentication policy`.  Common usage might look
CM 140     like so within the body of a view function (``response`` is
141     assumed to be a :term:`WebOb` -style :term:`response` object
06ecaf 142     computed previously by the view code):
0184b5 143
06ecaf 144     .. code-block:: python
0184b5 145
CM 146        from pyramid.security import remember
147        headers = remember(request, 'chrism', password='123', max_age='86400')
0dcd56 148        response = request.response
0184b5 149        response.headerlist.extend(headers)
CM 150        return response
151
152     If no :term:`authentication policy` is in use, this function will
072a2c 153     always return an empty sequence. If used, the composition and
0184b5 154     meaning of ``**kw`` must be agreed upon by the calling code and
CM 155     the effective authentication policy.
781371 156
MM 157     .. versionchanged:: 1.6
158         Deprecated the ``principal`` argument in favor of ``userid`` to clarify
159         its relationship to the authentication policy.
160
161     .. versionchanged:: 1.10
162         Removed the deprecated ``principal`` argument.
0184b5 163     """
0dcd56 164     policy = _get_authentication_policy(request)
CM 165     if policy is None:
166         return []
c7afe4 167     return policy.remember(request, userid, **kw)
3c2f95 168
0c29cf 169
0184b5 170 def forget(request):
CM 171     """
172     Return a sequence of header tuples (e.g. ``[('Set-Cookie',
173     'foo=abc')]``) suitable for 'forgetting' the set of credentials
174     possessed by the currently authenticated user.  A common usage
175     might look like so within the body of a view function
176     (``response`` is assumed to be an :term:`WebOb` -style
620bde 177     :term:`response` object computed previously by the view code):
0184b5 178
620bde 179     .. code-block:: python
MM 180
181        from pyramid.security import forget
182        headers = forget(request)
183        response.headerlist.extend(headers)
184        return response
0184b5 185
CM 186     If no :term:`authentication policy` is in use, this function will
187     always return an empty sequence.
0c29cf 188     """
0dcd56 189     policy = _get_authentication_policy(request)
CM 190     if policy is None:
191         return []
192     return policy.forget(request)
0c29cf 193
64ea2e 194
CM 195 def principals_allowed_by_permission(context, permission):
3e2f12 196     """ Provided a ``context`` (a resource object), and a ``permission``
0011d5 197     (a string or unicode object), if an :term:`authorization policy` is
8b1f6e 198     in effect, return a sequence of :term:`principal` ids that possess
CM 199     the permission in the ``context``.  If no authorization policy is
200     in effect, this will return a sequence with the single value
c81aad 201     :mod:`pyramid.security.Everyone` (the special principal
c6895b 202     identifier representing all principals).
a1a9fb 203
012b97 204     .. note::
M 205
0011d5 206        Even if an :term:`authorization policy` is in effect,
8b1f6e 207        some (exotic) authorization policies may not implement the
CM 208        required machinery for this function; those will cause a
c6895b 209        :exc:`NotImplementedError` exception to be raised when this
a1a9fb 210        function is invoked.
CM 211     """
41723e 212     reg = get_current_registry()
CM 213     policy = reg.queryUtility(IAuthorizationPolicy)
64ea2e 214     if policy is None:
CM 215         return [Everyone]
216     return policy.principals_allowed_by_permission(context, permission)
b54cdb 217
0c29cf 218
a1a9fb 219 def view_execution_permitted(context, request, name=''):
CM 220     """ If the view specified by ``context`` and ``name`` is protected
8b1f6e 221     by a :term:`permission`, check the permission associated with the
CM 222     view using the effective authentication/authorization policies and
223     the ``request``.  Return a boolean result.  If no
224     :term:`authorization policy` is in effect, or if the view is not
6e9640 225     protected by a permission, return ``True``. If no view can view found,
MM 226     an exception will be raised.
227
228     .. versionchanged:: 1.4a4
229        An exception is raised if no view is found.
230
231     """
3c2f95 232     reg = _get_registry(request)
475532 233     provides = [IViewClassifier] + map_(providedBy, (request, context))
2a842e 234     # XXX not sure what to do here about using _find_views or analogue;
CM 235     # for now let's just keep it as-is
41723e 236     view = reg.adapters.lookup(provides, ISecuredView, name=name)
d66bfb 237     if view is None:
4b552e 238         view = reg.adapters.lookup(provides, IView, name=name)
MM 239         if view is None:
0c29cf 240             raise TypeError(
MM 241                 'No registered view satisfies the constraints. '
242                 'It would not make sense to claim that this view '
243                 '"is" or "is not" permitted.'
244             )
a1a9fb 245         return Allowed(
0c29cf 246             'Allowed: view name %r in context %r (no permission defined)'
MM 247             % (name, context)
248         )
d66bfb 249     return view.__permitted__(context, request)
157721 250
012b97 251
7292d4 252 class PermitsResult(int):
CM 253     def __new__(cls, s, *args):
213001 254         """
MM 255         Create a new instance.
256
257         :param fmt: A format string explaining the reason for denial.
258         :param args: Arguments are stored and used with the format string
259                       to generate the ``msg``.
260
261         """
7292d4 262         inst = int.__new__(cls, cls.boolval)
CM 263         inst.s = s
264         inst.args = args
265         return inst
012b97 266
7292d4 267     @property
CM 268     def msg(self):
213001 269         """ A string indicating why the result was generated."""
7292d4 270         return self.s % self.args
CM 271
2466f6 272     def __str__(self):
17ce57 273         return self.msg
CM 274
275     def __repr__(self):
0c29cf 276         return '<%s instance at %s with msg %r>' % (
MM 277             self.__class__.__name__,
278             id(self),
279             self.msg,
280         )
281
2466f6 282
CM 283 class Denied(PermitsResult):
213001 284     """
MM 285     An instance of ``Denied`` is returned when a security-related
fd5ae9 286     API or other :app:`Pyramid` code denies an action unrelated to
c6895b 287     an ACL check.  It evaluates equal to all boolean false types.  It
CM 288     has an attribute named ``msg`` describing the circumstances for
213001 289     the deny.
MM 290
291     """
0c29cf 292
7292d4 293     boolval = 0
0c29cf 294
2466f6 295
CM 296 class Allowed(PermitsResult):
213001 297     """
MM 298     An instance of ``Allowed`` is returned when a security-related
fd5ae9 299     API or other :app:`Pyramid` code allows an action unrelated to
c6895b 300     an ACL check.  It evaluates equal to all boolean true types.  It
CM 301     has an attribute named ``msg`` describing the circumstances for
213001 302     the allow.
MM 303
304     """
0c29cf 305
7292d4 306     boolval = 1
0c29cf 307
f66290 308
213001 309 class ACLPermitsResult(PermitsResult):
7292d4 310     def __new__(cls, ace, acl, permission, principals, context):
213001 311         """
MM 312         Create a new instance.
313
314         :param ace: The :term:`ACE` that matched, triggering the result.
315         :param acl: The :term:`ACL` containing ``ace``.
316         :param permission: The required :term:`permission`.
317         :param principals: The list of :term:`principals <principal>` provided.
318         :param context: The :term:`context` providing the :term:`lineage`
319                         searched.
320
321         """
0c29cf 322         fmt = (
MM 323             '%s permission %r via ACE %r in ACL %r on context %r for '
324             'principals %r'
325         )
213001 326         inst = PermitsResult.__new__(
0c29cf 327             cls, fmt, cls.__name__, permission, ace, acl, context, principals
213001 328         )
7292d4 329         inst.permission = permission
CM 330         inst.ace = ace
331         inst.acl = acl
332         inst.principals = principals
333         inst.context = context
334         return inst
0c29cf 335
f66290 336
213001 337 class ACLDenied(ACLPermitsResult, Denied):
MM 338     """
339     An instance of ``ACLDenied`` is a specialization of
340     :class:`pyramid.security.Denied` that represents that a security check
341     made explicitly against ACL was denied.  It evaluates equal to all
342     boolean false types.  It also has the following attributes: ``acl``,
343     ``ace``, ``permission``, ``principals``, and ``context``.  These
344     attributes indicate the security values involved in the request.  Its
345     ``__str__`` method prints a summary of these attributes for debugging
346     purposes. The same summary is available as the ``msg`` attribute.
17ce57 347
213001 348     """
7292d4 349
0c29cf 350
213001 351 class ACLAllowed(ACLPermitsResult, Allowed):
MM 352     """
353     An instance of ``ACLAllowed`` is a specialization of
354     :class:`pyramid.security.Allowed` that represents that a security check
355     made explicitly against ACL was allowed.  It evaluates equal to all
356     boolean true types.  It also has the following attributes: ``acl``,
357     ``ace``, ``permission``, ``principals``, and ``context``.  These
358     attributes indicate the security values involved in the request.  Its
359     ``__str__`` method prints a summary of these attributes for debugging
360     purposes. The same summary is available as the ``msg`` attribute.
7292d4 361
213001 362     """
7d1da8 363
3c2f95 364
0c29cf 365 class AuthenticationAPIMixin(object):
3c2f95 366     def _get_authentication_policy(self):
MR 367         reg = _get_registry(self)
368         return reg.queryUtility(IAuthenticationPolicy)
369
370     @property
371     def authenticated_userid(self):
372         """ Return the userid of the currently authenticated user or
373         ``None`` if there is no :term:`authentication policy` in effect or
0184b5 374         there is no currently authenticated user.
CM 375
376         .. versionadded:: 1.5
377         """
3c2f95 378         policy = self._get_authentication_policy()
MR 379         if policy is None:
380             return None
381         return policy.authenticated_userid(self)
382
383     @property
384     def unauthenticated_userid(self):
385         """ Return an object which represents the *claimed* (not verified) user
386         id of the credentials present in the request. ``None`` if there is no
387         :term:`authentication policy` in effect or there is no user data
388         associated with the current request.  This differs from
e18385 389         :attr:`~pyramid.request.Request.authenticated_userid`, because the
CM 390         effective authentication policy will not ensure that a record
391         associated with the userid exists in persistent storage.
0184b5 392
CM 393         .. versionadded:: 1.5
394         """
3c2f95 395         policy = self._get_authentication_policy()
MR 396         if policy is None:
397             return None
398         return policy.unauthenticated_userid(self)
399
400     @property
401     def effective_principals(self):
402         """ Return the list of 'effective' :term:`principal` identifiers
82ec99 403         for the ``request``. If no :term:`authentication policy` is in effect,
MM 404         this will return a one-element list containing the
405         :data:`pyramid.security.Everyone` principal.
0184b5 406
CM 407         .. versionadded:: 1.5
408         """
3c2f95 409         policy = self._get_authentication_policy()
MR 410         if policy is None:
411             return [Everyone]
412         return policy.effective_principals(self)
dd1523 413
3c2f95 414
0c29cf 415 class AuthorizationAPIMixin(object):
3c2f95 416     def has_permission(self, permission, context=None):
e0d1af 417         """ Given a permission and an optional context, returns an instance of
CM 418         :data:`pyramid.security.Allowed` if the permission is granted to this
419         request with the provided context, or the context already associated
420         with the request.  Otherwise, returns an instance of
421         :data:`pyramid.security.Denied`.  This method delegates to the current
422         authentication and authorization policies.  Returns
423         :data:`pyramid.security.Allowed` unconditionally if no authentication
424         policy has been registered for this request.  If ``context`` is not
425         supplied or is supplied as ``None``, the context used is the
426         ``request.context`` attribute.
3c2f95 427
MR 428         :param permission: Does this request have the given permission?
429         :type permission: unicode, str
c12603 430         :param context: A resource object or ``None``
3c2f95 431         :type context: object
213001 432         :returns: Either :class:`pyramid.security.Allowed` or
MM 433                   :class:`pyramid.security.Denied`.
0184b5 434
CM 435         .. versionadded:: 1.5
436
3c2f95 437         """
MR 438         if context is None:
439             context = self.context
440         reg = _get_registry(self)
441         authn_policy = reg.queryUtility(IAuthenticationPolicy)
442         if authn_policy is None:
443             return Allowed('No authentication policy in use.')
444         authz_policy = reg.queryUtility(IAuthorizationPolicy)
445         if authz_policy is None:
0c29cf 446             raise ValueError(
MM 447                 'Authentication policy registered without '
448                 'authorization policy'
449             )  # should never happen
3c2f95 450         principals = authn_policy.effective_principals(self)
MR 451         return authz_policy.permits(context, principals, permission)