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