Michael Merickel
2018-10-15 2b024920847481592b1a13d4006d2a9fa8881d72
commit | author | age
de3d0c 1 from zope.interface import implementer
MM 2
ee117e 3 from pyramid.interfaces import (
CM 4     IAuthorizationPolicy,
5     IAuthenticationPolicy,
fe0d22 6     ICSRFStoragePolicy,
de3d0c 7     IDefaultCSRFOptions,
ee117e 8     IDefaultPermission,
CM 9     PHASE1_CONFIG,
10     PHASE2_CONFIG,
11     )
5bf23f 12
682a9b 13 from pyramid.csrf import LegacySessionCSRFStoragePolicy
5bf23f 14 from pyramid.exceptions import ConfigurationError
c7974f 15 from pyramid.util import as_sorted_tuple
a2c7c7 16
52fde9 17 from pyramid.config.util import action_method
5bf23f 18
CM 19 class SecurityConfiguratorMixin(object):
7c0f09 20
MW 21     def add_default_security(self):
682a9b 22         self.set_csrf_storage_policy(LegacySessionCSRFStoragePolicy())
7c0f09 23
5bf23f 24     @action_method
CM 25     def set_authentication_policy(self, policy):
26         """ Override the :app:`Pyramid` :term:`authentication policy` in the
27         current configuration.  The ``policy`` argument must be an instance
28         of an authentication policy or a :term:`dotted Python name`
29         that points at an instance of an authentication policy.
adfc23 30
012b97 31         .. note::
M 32
33            Using the ``authentication_policy`` argument to the
34            :class:`pyramid.config.Configurator` constructor can be used to
35            achieve the same purpose.
36
5bf23f 37         """
eb2fee 38         def register():
CM 39             self._set_authentication_policy(policy)
5bf23f 40             if self.registry.queryUtility(IAuthorizationPolicy) is None:
CM 41                 raise ConfigurationError(
42                     'Cannot configure an authentication policy without '
43                     'also configuring an authorization policy '
eb2fee 44                     '(use the set_authorization_policy method)')
7f72f8 45         intr = self.introspectable('authentication policy', None,
CM 46                                    self.object_description(policy),
47                                    'authentication policy')
48         intr['policy'] = policy
eb2fee 49         # authentication policy used by view config (phase 3)
7f72f8 50         self.action(IAuthenticationPolicy, register, order=PHASE2_CONFIG,
CM 51                     introspectables=(intr,))
5bf23f 52
CM 53     def _set_authentication_policy(self, policy):
54         policy = self.maybe_dotted(policy)
55         self.registry.registerUtility(policy, IAuthenticationPolicy)
56
57     @action_method
58     def set_authorization_policy(self, policy):
59         """ Override the :app:`Pyramid` :term:`authorization policy` in the
60         current configuration.  The ``policy`` argument must be an instance
61         of an authorization policy or a :term:`dotted Python name` that points
62         at an instance of an authorization policy.
adfc23 63
012b97 64         .. note::
M 65
66            Using the ``authorization_policy`` argument to the
67            :class:`pyramid.config.Configurator` constructor can be used to
68            achieve the same purpose.
5bf23f 69         """
eb2fee 70         def register():
CM 71             self._set_authorization_policy(policy)
f67ba4 72         def ensure():
CM 73             if self.autocommit:
74                 return
75             if self.registry.queryUtility(IAuthenticationPolicy) is None:
76                 raise ConfigurationError(
77                     'Cannot configure an authorization policy without '
78                     'also configuring an authentication policy '
79                     '(use the set_authorization_policy method)')
012b97 80
7f72f8 81         intr = self.introspectable('authorization policy', None,
CM 82                                    self.object_description(policy),
83                                    'authorization policy')
84         intr['policy'] = policy
eb2fee 85         # authorization policy used by view config (phase 3) and
CM 86         # authentication policy (phase 2)
1b7342 87         self.action(IAuthorizationPolicy, register, order=PHASE1_CONFIG,
CM 88                     introspectables=(intr,))
3171fb 89         self.action(None, ensure)
5bf23f 90
CM 91     def _set_authorization_policy(self, policy):
92         policy = self.maybe_dotted(policy)
93         self.registry.registerUtility(policy, IAuthorizationPolicy)
94
95     @action_method
96     def set_default_permission(self, permission):
97         """
98         Set the default permission to be used by all subsequent
99         :term:`view configuration` registrations.  ``permission``
100         should be a :term:`permission` string to be used as the
101         default permission.  An example of a permission
102         string:``'view'``.  Adding a default permission makes it
103         unnecessary to protect each view configuration with an
104         explicit permission, unless your application policy requires
105         some exception for a particular view.
106
107         If a default permission is *not* set, views represented by
108         view configuration registrations which do not explicitly
109         declare a permission will be executable by entirely anonymous
110         users (any authorization policy is ignored).
111
112         Later calls to this method override will conflict with earlier calls;
113         there can be only one default permission active at a time within an
114         application.
115
116         .. warning::
117
118           If a default permission is in effect, view configurations meant to
119           create a truly anonymously accessible view (even :term:`exception
adfc23 120           view` views) *must* use the value of the permission importable as
CM 121           :data:`pyramid.security.NO_PERMISSION_REQUIRED`.  When this string
122           is used as the ``permission`` for a view configuration, the default
123           permission is ignored, and the view is registered, making it
124           available to all callers regardless of their credentials.
5bf23f 125
2033ee 126         .. seealso::
SP 127
128             See also :ref:`setting_a_default_permission`.
5bf23f 129
012b97 130         .. note::
M 131
132            Using the ``default_permission`` argument to the
133            :class:`pyramid.config.Configurator` constructor can be used to
134            achieve the same purpose.
5bf23f 135         """
eb2fee 136         def register():
CM 137             self.registry.registerUtility(permission, IDefaultPermission)
7f72f8 138         intr = self.introspectable('default permission',
CM 139                                    None,
140                                    permission,
141                                    'default permission')
142         intr['value'] = permission
522405 143         perm_intr = self.introspectable('permissions',
CM 144                                         permission,
145                                         permission,
146                                         'permission')
7f72f8 147         perm_intr['value'] = permission
CM 148         # default permission used during view registration (phase 3)
149         self.action(IDefaultPermission, register, order=PHASE1_CONFIG,
150                     introspectables=(intr, perm_intr,))
5bf23f 151
6b180c 152     def add_permission(self, permission_name):
CM 153         """
154         A configurator directive which registers a free-standing
155         permission without associating it with a view callable.  This can be
156         used so that the permission shows up in the introspectable data under
157         the ``permissions`` category (permissions mentioned via ``add_view``
158         already end up in there).  For example::
159
160           config = Configurator()
161           config.add_permission('view')
162         """
163         intr = self.introspectable(
164             'permissions',
165             permission_name,
166             permission_name,
167             'permission'
168             )
169         intr['value'] = permission_name
170         self.action(None, introspectables=(intr,))
171
de3d0c 172     @action_method
MM 173     def set_default_csrf_options(
174         self,
175         require_csrf=True,
176         token='csrf_token',
177         header='X-CSRF-Token',
178         safe_methods=('GET', 'HEAD', 'OPTIONS', 'TRACE'),
17fa5e 179         callback=None,
de3d0c 180     ):
MM 181         """
182         Set the default CSRF options used by subsequent view registrations.
183
184         ``require_csrf`` controls whether CSRF checks will be automatically
185         enabled on each view in the application. This value is used as the
186         fallback when ``require_csrf`` is left at the default of ``None`` on
187         :meth:`pyramid.config.Configurator.add_view`.
188
189         ``token`` is the name of the CSRF token used in the body of the
190         request, accessed via ``request.POST[token]``. Default: ``csrf_token``.
191
192         ``header`` is the name of the header containing the CSRF token,
193         accessed via ``request.headers[header]``. Default: ``X-CSRF-Token``.
194
195         If ``token`` or ``header`` are set to ``None`` they will not be used
196         for checking CSRF tokens.
197
198         ``safe_methods`` is an iterable of HTTP methods which are expected to
199         not contain side-effects as defined by RFC2616. Safe methods will
200         never be automatically checked for CSRF tokens.
201         Default: ``('GET', 'HEAD', 'OPTIONS', TRACE')``.
202
17fa5e 203         If ``callback`` is set, it must be a callable accepting ``(request)``
MM 204         and returning ``True`` if the request should be checked for a valid
205         CSRF token. This callback allows an application to support
206         alternate authentication methods that do not rely on cookies which
207         are not subject to CSRF attacks. For example, if a request is
208         authenticated using the ``Authorization`` header instead of a cookie,
209         this may return ``False`` for that request so that clients do not
859755 210         need to send the ``X-CSRF-Token`` header. The callback is only tested
17fa5e 211         for non-safe methods as defined by ``safe_methods``.
MM 212
2078f2 213         .. versionadded:: 1.7
MM 214
215         .. versionchanged:: 1.8
216            Added the ``callback`` option.
217
de3d0c 218         """
17fa5e 219         options = DefaultCSRFOptions(
MM 220             require_csrf, token, header, safe_methods, callback,
221         )
de3d0c 222         def register():
MM 223             self.registry.registerUtility(options, IDefaultCSRFOptions)
224         intr = self.introspectable('default csrf view options',
225                                    None,
226                                    options,
227                                    'default csrf view options')
228         intr['require_csrf'] = require_csrf
229         intr['token'] = token
230         intr['header'] = header
231         intr['safe_methods'] = as_sorted_tuple(safe_methods)
17fa5e 232         intr['callback'] = callback
a2c7c7 233
de3d0c 234         self.action(IDefaultCSRFOptions, register, order=PHASE1_CONFIG,
MM 235                     introspectables=(intr,))
236
7c0f09 237     @action_method
MW 238     def set_csrf_storage_policy(self, policy):
239         """
682a9b 240         Set the :term:`CSRF storage policy` used by subsequent view
MM 241         registrations.
7c0f09 242
MW 243         ``policy`` is a class that implements the
682a9b 244         :meth:`pyramid.interfaces.ICSRFStoragePolicy` interface and defines
MM 245         how to generate and persist CSRF tokens.
246
7c0f09 247         """
MW 248         def register():
249             self.registry.registerUtility(policy, ICSRFStoragePolicy)
682a9b 250         intr = self.introspectable('csrf storage policy',
MM 251                                    None,
252                                    policy,
253                                    'csrf storage policy')
254         intr['policy'] = policy
255         self.action(ICSRFStoragePolicy, register, introspectables=(intr,))
7c0f09 256
a2c7c7 257
de3d0c 258 @implementer(IDefaultCSRFOptions)
MM 259 class DefaultCSRFOptions(object):
17fa5e 260     def __init__(self, require_csrf, token, header, safe_methods, callback):
de3d0c 261         self.require_csrf = require_csrf
MM 262         self.token = token
263         self.header = header
264         self.safe_methods = frozenset(safe_methods)
17fa5e 265         self.callback = callback