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