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