pyramid-csrf-multi-scheme 1.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ # stdlib
2
+ import sys
3
+ import uuid
4
+
5
+ # pypi
6
+ from pyramid.interfaces import ICSRFStoragePolicy
7
+ from pyramid.request import Request
8
+ from pyramid.util import strings_differ
9
+ from pyramid.util import SimpleSerializer
10
+ from webob.cookies import CookieProfile
11
+ from webob.response import Response
12
+ from zope.interface import implementer
13
+
14
+
15
+ # ==============================================================================
16
+
17
+
18
+ __VERSION__ = "1.0.0"
19
+
20
+
21
+ # ------------------------------------------------------------------------------
22
+
23
+
24
+ """
25
+ The following functions are taken from Pyramid directly.
26
+
27
+ * `pyramid.compat.bytes_`
28
+ * `pyramid.compat.text_`
29
+ """
30
+
31
+ text_type = unicode if (sys.version_info[0] == 2) else str
32
+ binary_type = str if (sys.version_info[0] == 2) else bytes
33
+
34
+
35
+ def text_(s, encoding="latin-1", errors="strict"):
36
+ """If ``s`` is an instance of ``binary_type``, return
37
+ ``s.decode(encoding, errors)``, otherwise return ``s``"""
38
+ if isinstance(s, binary_type):
39
+ return s.decode(encoding, errors)
40
+ return s
41
+
42
+
43
+ def bytes_(s, encoding="latin-1", errors="strict"):
44
+ """If ``s`` is an instance of ``text_type``, return
45
+ ``s.encode(encoding, errors)``, otherwise return ``s``"""
46
+ if isinstance(s, text_type):
47
+ return s.encode(encoding, errors)
48
+ return s
49
+
50
+
51
+ # ==============================================================================
52
+
53
+
54
+ @implementer(ICSRFStoragePolicy)
55
+ class DualCookieCSRFStoragePolicy(object):
56
+ """
57
+ A re-implementation of ``pyramid.csrf.CookieCSRFStoragePolicy``.
58
+ This policy runs separate csrf tokens on http and https connections.
59
+
60
+ If the connection is https:
61
+ expiring the csrf will expire the http and https
62
+ only the https csrf will be consulted
63
+
64
+ If the connection is http:
65
+ expiring the csrf will expire the http
66
+ only the http csrf will be consulted
67
+
68
+ last synced to pyramid: 1.10.4
69
+
70
+ NOTE: this does not support a ``secure`` argument
71
+ """
72
+
73
+ _token_factory = staticmethod(lambda: text_(uuid.uuid4().hex))
74
+
75
+ def __init__(
76
+ self,
77
+ cookie_name_secure="csrf_https",
78
+ cookie_name_http="csrf_http",
79
+ secure=None,
80
+ httponly=False,
81
+ domain=None,
82
+ max_age=None,
83
+ path="/",
84
+ samesite="Lax",
85
+ ):
86
+ if secure is not None:
87
+ raise ValueError(
88
+ "`DualCookieCSRFStoragePolicy` does not support "
89
+ "the `secure` argument."
90
+ )
91
+ serializer = SimpleSerializer()
92
+ self.cookie_name_secure = cookie_name_secure
93
+ self.cookie_name_http = cookie_name_http
94
+ self.cookie_profile_secure = CookieProfile(
95
+ cookie_name=cookie_name_secure,
96
+ secure=True,
97
+ max_age=max_age,
98
+ httponly=httponly,
99
+ path=path,
100
+ domains=[domain],
101
+ serializer=serializer,
102
+ samesite=samesite,
103
+ )
104
+ self.cookie_profile_http = CookieProfile(
105
+ cookie_name=cookie_name_http,
106
+ secure=False,
107
+ max_age=max_age,
108
+ httponly=httponly,
109
+ path=path,
110
+ domains=[domain],
111
+ serializer=serializer,
112
+ samesite=samesite,
113
+ )
114
+
115
+ @property
116
+ def cookie_profile(self):
117
+ """
118
+ Returns the active cookie profile for the request's scheme.
119
+ """
120
+ raise ValueError(
121
+ "`DualCookieCSRFStoragePolicy` supports access "
122
+ "via the attributes `.cookie_profile_secure` and "
123
+ "via the attributes `.cookie_profile_http`. The "
124
+ "attribute `.cookie_profile` is not supported."
125
+ )
126
+
127
+ def new_csrf_token(self, request):
128
+ # type: (Request) -> str
129
+ """Rotate the HTTP token and also the HTTPS token on secure requests."""
130
+
131
+ def _issue_token(cookie_name, profile):
132
+ # type: (str, CookieProfile) -> str
133
+ """Store a fresh token and schedule its response cookie."""
134
+ token = self._token_factory()
135
+ request.cookies[cookie_name] = token
136
+
137
+ def _set_cookie(request, response):
138
+ # type: (Request, Response) -> None
139
+ profile.set_cookies(response, token)
140
+
141
+ request.add_response_callback(_set_cookie)
142
+ return token
143
+
144
+ token_http = _issue_token(self.cookie_name_http, self.cookie_profile_http)
145
+ if request.scheme == "https":
146
+ return _issue_token(self.cookie_name_secure, self.cookie_profile_secure)
147
+ return token_http
148
+
149
+ def get_csrf_token(self, request):
150
+ """
151
+ Returns the currently active CSRF token by checking the cookies
152
+ sent with the current request.
153
+ """
154
+ if request.scheme == "https":
155
+ # only consult the secure cookie
156
+ bound_cookies = self.cookie_profile_secure.bind(request)
157
+ else:
158
+ # the only accessible cookie is http insecure
159
+ bound_cookies = self.cookie_profile_http.bind(request)
160
+ token = bound_cookies.get_value()
161
+ if not token:
162
+ token = self.new_csrf_token(request)
163
+ return token
164
+
165
+ def check_csrf_token(self, request, supplied_token):
166
+ # type: (Request, object) -> bool
167
+ """Check a text or byte token using Pyramid's timing-safe comparison."""
168
+ if not isinstance(supplied_token, (text_type, binary_type)):
169
+ return False
170
+ try:
171
+ supplied_bytes = bytes_(supplied_token)
172
+ except UnicodeEncodeError:
173
+ return False
174
+ expected_token = self.get_csrf_token(request)
175
+ return not strings_differ(bytes_(expected_token), supplied_bytes)
176
+
177
+ def get_csrf_token_scheme(self, request, scheme):
178
+ """this is a utility for testing"""
179
+ if scheme not in ("https", "http"):
180
+ raise ValueError("unknown scheme")
181
+ if scheme == "https":
182
+ if request.scheme != "https":
183
+ return None
184
+ bound_cookies = self.cookie_profile_secure.bind(request)
185
+ token = bound_cookies.get_value()
186
+ return token
187
+ bound_cookies = self.cookie_profile_http.bind(request)
188
+ token = bound_cookies.get_value()
189
+ return token
@@ -0,0 +1,8 @@
1
+ from .panels.csrf_multi_scheme import CSRFMultiSchemeDebugPanel
2
+
3
+
4
+ def includeme(config):
5
+ """
6
+ Pyramid API hook
7
+ """
8
+ config.add_debugtoolbar_panel(CSRFMultiSchemeDebugPanel)
@@ -0,0 +1,116 @@
1
+ from pyramid_debugtoolbar.panels import DebugPanel
2
+
3
+ from pyramid.interfaces import ICSRFStoragePolicy
4
+ from ... import DualCookieCSRFStoragePolicy
5
+
6
+ _ = lambda x: x
7
+
8
+
9
+ class CSRFMultiSchemeDebugPanel(DebugPanel):
10
+ """
11
+ Sample debug panel
12
+ """
13
+
14
+ name = "CSRFMultiScheme"
15
+ has_content = None
16
+ template = "pyramid_csrf_multi_scheme.debugtoolbar.panels:templates/csrf_multi_scheme.dbtmako"
17
+
18
+ # only query the policy once
19
+ _policy = None
20
+
21
+ # stash
22
+ _cookie_names = None
23
+
24
+ response = None # we may never have a response
25
+
26
+ def __init__(self, request):
27
+
28
+ self.data = {
29
+ "request_data": {
30
+ "cookie_secure": {"in": None, "out": None, "cookie_name": None},
31
+ "cookie_http": {"in": None, "out": None, "cookie_name": None},
32
+ }
33
+ }
34
+ policy = request.registry.queryUtility(ICSRFStoragePolicy)
35
+ if isinstance(policy, DualCookieCSRFStoragePolicy):
36
+ self._policy = policy
37
+ self._cookie_names = [policy.cookie_name_secure, policy.cookie_name_http]
38
+
39
+ self.has_content = True
40
+
41
+ self.data["request_data"]["cookie_secure"]["in"] = request.cookies.get(
42
+ policy.cookie_name_secure
43
+ )
44
+ self.data["request_data"]["cookie_secure"][
45
+ "cookie_name"
46
+ ] = policy.cookie_name_secure
47
+
48
+ self.data["request_data"]["cookie_http"]["in"] = request.cookies.get(
49
+ policy.cookie_name_http
50
+ )
51
+ self.data["request_data"]["cookie_http"][
52
+ "cookie_name"
53
+ ] = policy.cookie_name_http
54
+
55
+ # `process_response` fires before the set_cookie callbacks, so use this hack...
56
+ def finished_callback(request):
57
+ self._process_response_deferred()
58
+
59
+ request.add_finished_callback(finished_callback)
60
+
61
+ def process_response(self, response):
62
+ """`
63
+ process_response` fires before the set_cookie callbacks
64
+ so we use a hack from pyramid_debugtoolbar's HeaderDebugPanel
65
+
66
+ 1. `__init__()` sets a callback to `_process_response_deferred()`
67
+ 2. `process_response()` stashes the response
68
+ 3. `_process_response_deferred` has the headers
69
+ """
70
+ self.response = response
71
+
72
+ def _process_response_deferred(self):
73
+ """
74
+ `process_response` fires before the set_cookie callbacks
75
+ so we use a hack from pyramid_debugtoolbar's HeaderDebugPanel
76
+ """
77
+ if self.response is not None:
78
+ cookies = [i for i in self.response.headerlist if i[0] == "Set-Cookie"]
79
+ # [('Set-Cookie', 'ci_ss_ck=eXrTEtumW4Jal3FyEXtkJijofCAyOnayzGQiPG7IAjukycfE; Domain=.dev.example.com; Path=/'),
80
+ # ('Set-Cookie', 'ci_s_ck=iG6dqE5mff_iR2Y95o5eENuVdC; Domain=.dev.example.com; Path=/')
81
+ # ]
82
+ for ck in cookies:
83
+ try:
84
+ ck_parts = ck[1].split(";")
85
+ (ck_name, ck_value) = ck_parts[0].split(
86
+ "=", 1
87
+ ) # maxsplit 1, because the value could be an encoding with == as a right pad
88
+ if ck_name in self._cookie_names:
89
+ if (
90
+ ck_name
91
+ == self.data["request_data"]["cookie_secure"]["cookie_name"]
92
+ ):
93
+ self.data["request_data"]["cookie_secure"]["out"] = ck_value
94
+ elif (
95
+ ck_name
96
+ == self.data["request_data"]["cookie_http"]["cookie_name"]
97
+ ):
98
+ self.data["request_data"]["cookie_http"]["out"] = ck_value
99
+ except Exception as exc:
100
+ # TODO: logging
101
+ pass
102
+
103
+ @property
104
+ def nav_title(self):
105
+ return _(self.name)
106
+
107
+ @property
108
+ def title(self):
109
+ return _(self.name)
110
+
111
+ @property
112
+ def url(self):
113
+ return ""
114
+
115
+ def render_content(self, request):
116
+ return DebugPanel.render_content(self, request)
@@ -0,0 +1,34 @@
1
+ <%!
2
+ def coded_value(val):
3
+ if val:
4
+ return "<code>%s</code>" % val
5
+ return ''
6
+ %>
7
+
8
+ <table class="table table-striped table-condensed">
9
+ <thead>
10
+ <tr>
11
+ <th>cookie_type</th>
12
+ <th>cookie_secure</th>
13
+ <th>cookie_http</th>
14
+ </tr>
15
+ </thead>
16
+ <tbody>
17
+ <tr>
18
+ <th>cookie_name</th>
19
+ <td><code>${request_data['cookie_secure']['cookie_name']}</code></td>
20
+ <td><code>${request_data['cookie_http']['cookie_name']}</code></td>
21
+ </tr>
22
+ <tr>
23
+ <th>request</th>
24
+ <td>${coded_value(request_data['cookie_secure']['in'])|n}</td>
25
+ <td>${coded_value(request_data['cookie_http']['in'])|n}</td>
26
+ </tr>
27
+ <tr>
28
+ <th>response</th>
29
+ <td>${coded_value(request_data['cookie_secure']['out'])|n}</td>
30
+ <td>${coded_value(request_data['cookie_http']['out'])|n}</td>
31
+ </tr>
32
+ </tbody>
33
+ </table>
34
+
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyramid_csrf_multi_scheme
3
+ Version: 1.0.0
4
+ Summary: Provides for creating independent csrf tokens for the http and https schemes
5
+ Home-page: https://github.com/jvanasco/pyramid_csrf_multi_scheme
6
+ Author: Jonathan Vanasco
7
+ Author-email: jvanasco@2xlp.com
8
+ License: BSD-derived (http://www.repoze.org/LICENSE.txt)
9
+ Keywords: web,pyramid,csrf
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Framework :: Pyramid
12
+ Classifier: Programming Language :: Python :: 2.7
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.5
15
+ Classifier: Programming Language :: Python :: 3.6
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Programming Language :: Python :: 3.14
24
+ Classifier: Programming Language :: Python :: 3.15
25
+ Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
26
+ Description-Content-Type: text/x-rst
27
+ License-File: LICENSE.txt
28
+ Requires-Dist: pyramid>=1.10.4
29
+ Requires-Dist: setuptools<82; python_version >= "3.7" and python_version < "3.10"
30
+ Provides-Extra: testing
31
+ Requires-Dist: coverage; extra == "testing"
32
+ Requires-Dist: pytest; extra == "testing"
33
+ Requires-Dist: pyramid_debugtoolbar>=4.0.0; extra == "testing"
34
+ Dynamic: license-file
35
+
36
+ pyramid_csrf_multi_scheme
37
+ =========================
38
+
39
+ .. image:: https://github.com/jvanasco/pyramid_csrf_multi_scheme/workflows/Python%20package/badge.svg
40
+ :alt: Build Status
41
+
42
+ This package enables two separate cookie tokens on each request, bound to the scheme:
43
+
44
+ * a SECURE HTTPS only cookie
45
+ * a mixed-use insecure http token (which is also available on https).
46
+
47
+ If the current scheme is `HTTPS`:
48
+
49
+ * only the SECURE HTTPS token will be considered
50
+ * HOWEVER calls to generate a new token will reset both the SECURE HTTPS and the
51
+ insecure http tokens.
52
+
53
+ If the current scheme is `insecure http`:
54
+
55
+ * the SECURE HTTPS tokens are ignored as they are not even available, and only the
56
+ insecure http token is considered.
57
+
58
+
59
+ Python Versions
60
+ ---------------
61
+
62
+ This package is currently supported under Python2.7 and 3.8; other 3.x versions may work.
63
+
64
+ This package should work on Pyramid1.x and 2.x
65
+
66
+
67
+ Why?
68
+ ----
69
+
70
+ If an application supports both `HTTP` and `HTTPS` endpoints, this package simplifies
71
+ isolating the CSRF data from both.
72
+
73
+
74
+ Is this necessary?
75
+ ------------------
76
+
77
+ I'm not sure, but have decided to err on the side of caution.
78
+
79
+ HTTP traffic is sent in plaintext and capable of being intercepted by a
80
+ man-in-the-middle or network packet sniffing.
81
+
82
+ It seems plausible that someone might read a csrf token via HTTP and use that
83
+ in attempts to compromise HTTPS endpoints in a mixed use environment.
84
+
85
+ A better option is to only use HTTPS tokens and forms - but that is not
86
+ always an option.
87
+
88
+
89
+ debugtoolbar support!
90
+ ---------------------
91
+
92
+ just add to your `ENVIRONMENT.ini` file, or similar application configuration.
93
+
94
+ .. code-block:: python
95
+
96
+ debugtoolbar.includes = pyramid_csrf_multi_scheme.debugtoolbar
97
+
98
+ Tthe debugtoolbar will now have a `CSRFMultiScheme` panel that has the following info:
99
+
100
+ * configuration info on the cookie names
101
+ * incoming request csrf values
102
+ * outgoing response csrf values
103
+
104
+
105
+ License
106
+ -------
107
+
108
+ Most of this is just code lightly edited from Pyramid, and
109
+ therefore available under Pyramid's licensing terms.
110
+
@@ -0,0 +1,10 @@
1
+ pyramid_csrf_multi_scheme/__init__.py,sha256=hg1nrVe51ZXv23T0RsnrgxHSfUGMpr9tsomMDrUUOjg,6187
2
+ pyramid_csrf_multi_scheme/debugtoolbar/__init__.py,sha256=y-09Tp_6atpkVbdJKYlXBRNJU5Id0H9S1GyLWkASj1E,187
3
+ pyramid_csrf_multi_scheme/debugtoolbar/panels/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ pyramid_csrf_multi_scheme/debugtoolbar/panels/csrf_multi_scheme.py,sha256=V8vP7emxqTWwJ8WrNVjzpmRdTVMRMsRoSvZWeZ-SCDI,4130
5
+ pyramid_csrf_multi_scheme/debugtoolbar/panels/templates/csrf_multi_scheme.dbtmako,sha256=LHQSj8Sfv57fEpVkp-e1eTlZY6w3k9NOW72Mz1x0KLk,792
6
+ pyramid_csrf_multi_scheme-1.0.0.dist-info/licenses/LICENSE.txt,sha256=GiTI_rOhwQPIHLvhwbVxVrBcpi3Q-9vC0iqWjPHJeXE,1850
7
+ pyramid_csrf_multi_scheme-1.0.0.dist-info/METADATA,sha256=RmCVoxDd1Qv0jolfidWruZfsC-jXAvI20n4e-5Wj414,3562
8
+ pyramid_csrf_multi_scheme-1.0.0.dist-info/WHEEL,sha256=Uvlg2aNlEKdilsyFSJt1QX5me0eEbCjQzR3nZeFkvy4,109
9
+ pyramid_csrf_multi_scheme-1.0.0.dist-info/top_level.txt,sha256=B88icNON_eQZtw45b_Ajo1m5Xos3YgI6WC-QNLoMB8w,26
10
+ pyramid_csrf_multi_scheme-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,6 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py2-none-any
5
+ Tag: py3-none-any
6
+
@@ -0,0 +1,40 @@
1
+ The majority of the code in Pyramid is supplied under this license:
2
+
3
+ A copyright notice accompanies this license document that identifies
4
+ the copyright holders.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are
8
+ met:
9
+
10
+ 1. Redistributions in source code must retain the accompanying
11
+ copyright notice, this list of conditions, and the following
12
+ disclaimer.
13
+
14
+ 2. Redistributions in binary form must reproduce the accompanying
15
+ copyright notice, this list of conditions, and the following
16
+ disclaimer in the documentation and/or other materials provided
17
+ with the distribution.
18
+
19
+ 3. Names of the copyright holders must not be used to endorse or
20
+ promote products derived from this software without prior
21
+ written permission from the copyright holders.
22
+
23
+ 4. If any files are modified, you must cause the modified files to
24
+ carry prominent notices stating that you changed the files and
25
+ the date of any change.
26
+
27
+ Disclaimer
28
+
29
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
30
+ ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
31
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
32
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33
+ HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
34
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
35
+ TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
37
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
38
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
39
+ THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40
+ SUCH DAMAGE.
@@ -0,0 +1 @@
1
+ pyramid_csrf_multi_scheme