We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Set samesite=lax on session cookies
This is a strong defense-in-depth mechanism for protecting against CSRF. It's currently only respected by Chrome, but Firefox will add it as well.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `warehouse/sessions.py`
Content:
```
1 # Licensed under the Apache License, Version 2.0 (the "License");
2 # you may not use this file except in compliance with the License.
3 # You may obtain a copy of the License at
4 #
5 # http://www.apache.org/licenses/LICENSE-2.0
6 #
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12 
13 import functools
14 import time
15 
16 import msgpack
17 import msgpack.exceptions
18 import redis
19 
20 from pyramid import viewderivers
21 from pyramid.interfaces import ISession, ISessionFactory
22 from zope.interface import implementer
23 
24 from warehouse.cache.http import add_vary
25 from warehouse.utils import crypto
26 
27 
28 def _invalid_method(method):
29     @functools.wraps(method)
30     def wrapped(self, *args, **kwargs):
31         self._error_message()
32     return wrapped
33 
34 
35 @implementer(ISession)
36 class InvalidSession(dict):
37 
38     __contains__ = _invalid_method(dict.__contains__)
39     __delitem__ = _invalid_method(dict.__delitem__)
40     __getitem__ = _invalid_method(dict.__getitem__)
41     __iter__ = _invalid_method(dict.__iter__)
42     __len__ = _invalid_method(dict.__len__)
43     __setitem__ = _invalid_method(dict.__setitem__)
44     clear = _invalid_method(dict.clear)
45     copy = _invalid_method(dict.copy)
46     fromkeys = _invalid_method(dict.fromkeys)
47     get = _invalid_method(dict.get)
48     items = _invalid_method(dict.items)
49     keys = _invalid_method(dict.keys)
50     pop = _invalid_method(dict.pop)
51     popitem = _invalid_method(dict.popitem)
52     setdefault = _invalid_method(dict.setdefault)
53     update = _invalid_method(dict.update)
54     values = _invalid_method(dict.values)
55 
56     def _error_message(self):
57         raise RuntimeError(
58             "Cannot use request.session in a view without uses_session=True."
59         )
60 
61     def __getattr__(self, name):
62         self._error_message()
63 
64     @property
65     def created(self):
66         self._error_message()
67 
68 
69 def _changed_method(method):
70     @functools.wraps(method)
71     def wrapped(self, *args, **kwargs):
72         self.changed()
73         return method(self, *args, **kwargs)
74     return wrapped
75 
76 
77 @implementer(ISession)
78 class Session(dict):
79 
80     _csrf_token_key = "_csrf_token"
81     _flash_key = "_flash_messages"
82 
83     # A number of our methods need to be decorated so that they also call
84     # self.changed()
85     __delitem__ = _changed_method(dict.__delitem__)
86     __setitem__ = _changed_method(dict.__setitem__)
87     clear = _changed_method(dict.clear)
88     pop = _changed_method(dict.pop)
89     popitem = _changed_method(dict.popitem)
90     setdefault = _changed_method(dict.setdefault)
91     update = _changed_method(dict.update)
92 
93     def __init__(self, data=None, session_id=None, new=True):
94         # Brand new sessions don't have any data, so we'll just create an empty
95         # dictionary for them.
96         if data is None:
97             data = {}
98 
99         # Initialize our actual dictionary here.
100         super().__init__(data)
101 
102         # We need to track the state of our Session.
103         self._sid = session_id
104         self._changed = False
105         self.new = new
106         self.created = int(time.time())
107 
108         # We'll track all of the IDs that have been invalidated here
109         self.invalidated = set()
110 
111     @property
112     def sid(self):
113         if self._sid is None:
114             self._sid = crypto.random_token()
115         return self._sid
116 
117     def changed(self):
118         self._changed = True
119 
120     def invalidate(self):
121         self.clear()
122         self.new = True
123         self.created = int(time.time())
124         self._changed = False
125 
126         # If the current session id isn't None we'll want to record it as one
127         # of the ones that have been invalidated.
128         if self._sid is not None:
129             self.invalidated.add(self._sid)
130             self._sid = None
131 
132     def should_save(self):
133         return self._changed
134 
135     # Flash Messages Methods
136     def _get_flash_queue_key(self, queue):
137         return ".".join(filter(None, [self._flash_key, queue]))
138 
139     def flash(self, msg, queue="", allow_duplicate=True):
140         queue_key = self._get_flash_queue_key(queue)
141 
142         # If we're not allowing duplicates check if this message is already
143         # in the queue, and if it is just return immediately.
144         if not allow_duplicate and msg in self[queue_key]:
145             return
146 
147         self.setdefault(queue_key, []).append(msg)
148 
149     def peek_flash(self, queue=""):
150         return self.get(self._get_flash_queue_key(queue), [])
151 
152     def pop_flash(self, queue=""):
153         queue_key = self._get_flash_queue_key(queue)
154         messages = self.get(queue_key, [])
155         self.pop(queue_key, None)
156         return messages
157 
158     # CSRF Methods
159     def new_csrf_token(self):
160         self[self._csrf_token_key] = crypto.random_token()
161         return self[self._csrf_token_key]
162 
163     def get_csrf_token(self):
164         token = self.get(self._csrf_token_key)
165         if token is None:
166             token = self.new_csrf_token()
167         return token
168 
169 
170 @implementer(ISessionFactory)
171 class SessionFactory:
172 
173     cookie_name = "session_id"
174     max_age = 12 * 60 * 60  # 12 hours
175 
176     def __init__(self, secret, url):
177         self.redis = redis.StrictRedis.from_url(url)
178         self.signer = crypto.TimestampSigner(secret, salt="session")
179 
180     def __call__(self, request):
181         return self._process_request(request)
182 
183     def _redis_key(self, session_id):
184         return "warehouse/session/data/{}".format(session_id)
185 
186     def _process_request(self, request):
187         # Register a callback with the request so we can save the session once
188         # it's finished.
189         request.add_response_callback(self._process_response)
190 
191         # Load our session ID from the request.
192         session_id = request.cookies.get(self.cookie_name)
193 
194         # If we do not have a session ID then we'll just use a new empty
195         # session.
196         if session_id is None:
197             return Session()
198 
199         # Check to make sure we have a valid session id
200         try:
201             session_id = self.signer.unsign(session_id, max_age=self.max_age)
202             session_id = session_id.decode("utf8")
203         except crypto.BadSignature:
204             return Session()
205 
206         # Fetch the serialized data from redis
207         bdata = self.redis.get(self._redis_key(session_id))
208 
209         # If the session didn't exist in redis, we'll give the user a new
210         # session.
211         if bdata is None:
212             return Session()
213 
214         # De-serialize our session data
215         try:
216             data = msgpack.unpackb(bdata, encoding="utf8", use_list=True)
217         except (msgpack.exceptions.UnpackException,
218                 msgpack.exceptions.ExtraData):
219             # If the session data was invalid we'll give the user a new session
220             return Session()
221 
222         # If we were able to load existing session data, load it into a
223         # Session class
224         session = Session(data, session_id, False)
225 
226         return session
227 
228     def _process_response(self, request, response):
229         # If the request has an InvalidSession, then the view can't have
230         # accessed the session, and we can just skip all of this anyways.
231         if isinstance(request.session, InvalidSession):
232             return
233 
234         # Check to see if the session has been marked to be deleted, if it has
235         # benn then we'll delete it, and tell our response to delete the
236         # session cookie as well.
237         if request.session.invalidated:
238             for session_id in request.session.invalidated:
239                 self.redis.delete(self._redis_key(session_id))
240 
241             if not request.session.should_save():
242                 response.delete_cookie(self.cookie_name)
243 
244         # Check to see if the session has been marked to be saved, generally
245         # this means that the session data has been modified and thus we need
246         # to store the new data.
247         if request.session.should_save():
248             # Save our session in Redis
249             self.redis.setex(
250                 self._redis_key(request.session.sid),
251                 self.max_age,
252                 msgpack.packb(
253                     request.session,
254                     encoding="utf8",
255                     use_bin_type=True,
256                 ),
257             )
258 
259             # Send our session cookie to the client
260             response.set_cookie(
261                 self.cookie_name,
262                 self.signer.sign(request.session.sid.encode("utf8")),
263                 max_age=self.max_age,
264                 httponly=True,
265                 secure=request.scheme == "https",
266             )
267 
268 
269 def session_view(view, info):
270     if info.options.get("uses_session"):
271         # If we're using the session, then we'll just return the original view
272         # with a small wrapper around it to ensure that it has a Vary: Cookie
273         # header.
274         return add_vary("Cookie")(view)
275     elif info.exception_only:
276         return view
277     else:
278         # If we're not using the session on this view, then we'll wrap the view
279         # with a wrapper that just ensures that the session cannot be used.
280         @functools.wraps(view)
281         def wrapped(context, request):
282             # This whole method is a little bit of an odd duck, we want to make
283             # sure that we don't actually *access* request.session, because
284             # doing so triggers the machinery to create a new session. So
285             # instead we will dig into the request object __dict__ to
286             # effectively do the same thing, jsut without triggering an access
287             # on request.session.
288 
289             # Save the original session so that we can restore it once the
290             # inner views have been called.
291             nothing = object()
292             original_session = request.__dict__.get("session", nothing)
293 
294             # This particular view hasn't been set to allow access to the
295             # session, so we'll just assign an InvalidSession to
296             # request.session
297             request.__dict__["session"] = InvalidSession()
298 
299             try:
300                 # Invoke the real view
301                 return view(context, request)
302             finally:
303                 # Restore the original session so that things like
304                 # pyramid_debugtoolbar can access it.
305                 if original_session is nothing:
306                     del request.__dict__["session"]
307                 else:
308                     request.__dict__["session"] = original_session
309 
310         return wrapped
311 
312 
313 session_view.options = {"uses_session"}
314 
315 
316 def includeme(config):
317     config.set_session_factory(
318         SessionFactory(
319             config.registry.settings["sessions.secret"],
320             config.registry.settings["sessions.url"],
321         ),
322     )
323 
324     config.add_view_deriver(
325         session_view,
326         over="csrf_view",
327         under=viewderivers.INGRESS,
328     )
329
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/warehouse/sessions.py b/warehouse/sessions.py
--- a/warehouse/sessions.py
+++ b/warehouse/sessions.py
@@ -263,6 +263,7 @@
                 max_age=self.max_age,
                 httponly=True,
                 secure=request.scheme == "https",
+                samesite=b"lax"
             )

{"golden_diff": "diff --git a/warehouse/sessions.py b/warehouse/sessions.py\n--- a/warehouse/sessions.py\n+++ b/warehouse/sessions.py\n@@ -263,6 +263,7 @@\n                 max_age=self.max_age,\n                 httponly=True,\n                 secure=request.scheme == \"https\",\n+                samesite=b\"lax\"\n             )\n", "issue": "Set samesite=lax on session cookies\nThis is a strong defense-in-depth mechanism for protecting against CSRF. It's currently only respected by Chrome, but Firefox will add it as well.\n", "before_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport functools\nimport time\n\nimport msgpack\nimport msgpack.exceptions\nimport redis\n\nfrom pyramid import viewderivers\nfrom pyramid.interfaces import ISession, ISessionFactory\nfrom zope.interface import implementer\n\nfrom warehouse.cache.http import add_vary\nfrom warehouse.utils import crypto\n\n\ndef _invalid_method(method):\n    @functools.wraps(method)\n    def wrapped(self, *args, **kwargs):\n        self._error_message()\n    return wrapped\n\n\n@implementer(ISession)\nclass InvalidSession(dict):\n\n    __contains__ = _invalid_method(dict.__contains__)\n    __delitem__ = _invalid_method(dict.__delitem__)\n    __getitem__ = _invalid_method(dict.__getitem__)\n    __iter__ = _invalid_method(dict.__iter__)\n    __len__ = _invalid_method(dict.__len__)\n    __setitem__ = _invalid_method(dict.__setitem__)\n    clear = _invalid_method(dict.clear)\n    copy = _invalid_method(dict.copy)\n    fromkeys = _invalid_method(dict.fromkeys)\n    get = _invalid_method(dict.get)\n    items = _invalid_method(dict.items)\n    keys = _invalid_method(dict.keys)\n    pop = _invalid_method(dict.pop)\n    popitem = _invalid_method(dict.popitem)\n    setdefault = _invalid_method(dict.setdefault)\n    update = _invalid_method(dict.update)\n    values = _invalid_method(dict.values)\n\n    def _error_message(self):\n        raise RuntimeError(\n            \"Cannot use request.session in a view without uses_session=True.\"\n        )\n\n    def __getattr__(self, name):\n        self._error_message()\n\n    @property\n    def created(self):\n        self._error_message()\n\n\ndef _changed_method(method):\n    @functools.wraps(method)\n    def wrapped(self, *args, **kwargs):\n        self.changed()\n        return method(self, *args, **kwargs)\n    return wrapped\n\n\n@implementer(ISession)\nclass Session(dict):\n\n    _csrf_token_key = \"_csrf_token\"\n    _flash_key = \"_flash_messages\"\n\n    # A number of our methods need to be decorated so that they also call\n    # self.changed()\n    __delitem__ = _changed_method(dict.__delitem__)\n    __setitem__ = _changed_method(dict.__setitem__)\n    clear = _changed_method(dict.clear)\n    pop = _changed_method(dict.pop)\n    popitem = _changed_method(dict.popitem)\n    setdefault = _changed_method(dict.setdefault)\n    update = _changed_method(dict.update)\n\n    def __init__(self, data=None, session_id=None, new=True):\n        # Brand new sessions don't have any data, so we'll just create an empty\n        # dictionary for them.\n        if data is None:\n            data = {}\n\n        # Initialize our actual dictionary here.\n        super().__init__(data)\n\n        # We need to track the state of our Session.\n        self._sid = session_id\n        self._changed = False\n        self.new = new\n        self.created = int(time.time())\n\n        # We'll track all of the IDs that have been invalidated here\n        self.invalidated = set()\n\n    @property\n    def sid(self):\n        if self._sid is None:\n            self._sid = crypto.random_token()\n        return self._sid\n\n    def changed(self):\n        self._changed = True\n\n    def invalidate(self):\n        self.clear()\n        self.new = True\n        self.created = int(time.time())\n        self._changed = False\n\n        # If the current session id isn't None we'll want to record it as one\n        # of the ones that have been invalidated.\n        if self._sid is not None:\n            self.invalidated.add(self._sid)\n            self._sid = None\n\n    def should_save(self):\n        return self._changed\n\n    # Flash Messages Methods\n    def _get_flash_queue_key(self, queue):\n        return \".\".join(filter(None, [self._flash_key, queue]))\n\n    def flash(self, msg, queue=\"\", allow_duplicate=True):\n        queue_key = self._get_flash_queue_key(queue)\n\n        # If we're not allowing duplicates check if this message is already\n        # in the queue, and if it is just return immediately.\n        if not allow_duplicate and msg in self[queue_key]:\n            return\n\n        self.setdefault(queue_key, []).append(msg)\n\n    def peek_flash(self, queue=\"\"):\n        return self.get(self._get_flash_queue_key(queue), [])\n\n    def pop_flash(self, queue=\"\"):\n        queue_key = self._get_flash_queue_key(queue)\n        messages = self.get(queue_key, [])\n        self.pop(queue_key, None)\n        return messages\n\n    # CSRF Methods\n    def new_csrf_token(self):\n        self[self._csrf_token_key] = crypto.random_token()\n        return self[self._csrf_token_key]\n\n    def get_csrf_token(self):\n        token = self.get(self._csrf_token_key)\n        if token is None:\n            token = self.new_csrf_token()\n        return token\n\n\n@implementer(ISessionFactory)\nclass SessionFactory:\n\n    cookie_name = \"session_id\"\n    max_age = 12 * 60 * 60  # 12 hours\n\n    def __init__(self, secret, url):\n        self.redis = redis.StrictRedis.from_url(url)\n        self.signer = crypto.TimestampSigner(secret, salt=\"session\")\n\n    def __call__(self, request):\n        return self._process_request(request)\n\n    def _redis_key(self, session_id):\n        return \"warehouse/session/data/{}\".format(session_id)\n\n    def _process_request(self, request):\n        # Register a callback with the request so we can save the session once\n        # it's finished.\n        request.add_response_callback(self._process_response)\n\n        # Load our session ID from the request.\n        session_id = request.cookies.get(self.cookie_name)\n\n        # If we do not have a session ID then we'll just use a new empty\n        # session.\n        if session_id is None:\n            return Session()\n\n        # Check to make sure we have a valid session id\n        try:\n            session_id = self.signer.unsign(session_id, max_age=self.max_age)\n            session_id = session_id.decode(\"utf8\")\n        except crypto.BadSignature:\n            return Session()\n\n        # Fetch the serialized data from redis\n        bdata = self.redis.get(self._redis_key(session_id))\n\n        # If the session didn't exist in redis, we'll give the user a new\n        # session.\n        if bdata is None:\n            return Session()\n\n        # De-serialize our session data\n        try:\n            data = msgpack.unpackb(bdata, encoding=\"utf8\", use_list=True)\n        except (msgpack.exceptions.UnpackException,\n                msgpack.exceptions.ExtraData):\n            # If the session data was invalid we'll give the user a new session\n            return Session()\n\n        # If we were able to load existing session data, load it into a\n        # Session class\n        session = Session(data, session_id, False)\n\n        return session\n\n    def _process_response(self, request, response):\n        # If the request has an InvalidSession, then the view can't have\n        # accessed the session, and we can just skip all of this anyways.\n        if isinstance(request.session, InvalidSession):\n            return\n\n        # Check to see if the session has been marked to be deleted, if it has\n        # benn then we'll delete it, and tell our response to delete the\n        # session cookie as well.\n        if request.session.invalidated:\n            for session_id in request.session.invalidated:\n                self.redis.delete(self._redis_key(session_id))\n\n            if not request.session.should_save():\n                response.delete_cookie(self.cookie_name)\n\n        # Check to see if the session has been marked to be saved, generally\n        # this means that the session data has been modified and thus we need\n        # to store the new data.\n        if request.session.should_save():\n            # Save our session in Redis\n            self.redis.setex(\n                self._redis_key(request.session.sid),\n                self.max_age,\n                msgpack.packb(\n                    request.session,\n                    encoding=\"utf8\",\n                    use_bin_type=True,\n                ),\n            )\n\n            # Send our session cookie to the client\n            response.set_cookie(\n                self.cookie_name,\n                self.signer.sign(request.session.sid.encode(\"utf8\")),\n                max_age=self.max_age,\n                httponly=True,\n                secure=request.scheme == \"https\",\n            )\n\n\ndef session_view(view, info):\n    if info.options.get(\"uses_session\"):\n        # If we're using the session, then we'll just return the original view\n        # with a small wrapper around it to ensure that it has a Vary: Cookie\n        # header.\n        return add_vary(\"Cookie\")(view)\n    elif info.exception_only:\n        return view\n    else:\n        # If we're not using the session on this view, then we'll wrap the view\n        # with a wrapper that just ensures that the session cannot be used.\n        @functools.wraps(view)\n        def wrapped(context, request):\n            # This whole method is a little bit of an odd duck, we want to make\n            # sure that we don't actually *access* request.session, because\n            # doing so triggers the machinery to create a new session. So\n            # instead we will dig into the request object __dict__ to\n            # effectively do the same thing, jsut without triggering an access\n            # on request.session.\n\n            # Save the original session so that we can restore it once the\n            # inner views have been called.\n            nothing = object()\n            original_session = request.__dict__.get(\"session\", nothing)\n\n            # This particular view hasn't been set to allow access to the\n            # session, so we'll just assign an InvalidSession to\n            # request.session\n            request.__dict__[\"session\"] = InvalidSession()\n\n            try:\n                # Invoke the real view\n                return view(context, request)\n            finally:\n                # Restore the original session so that things like\n                # pyramid_debugtoolbar can access it.\n                if original_session is nothing:\n                    del request.__dict__[\"session\"]\n                else:\n                    request.__dict__[\"session\"] = original_session\n\n        return wrapped\n\n\nsession_view.options = {\"uses_session\"}\n\n\ndef includeme(config):\n    config.set_session_factory(\n        SessionFactory(\n            config.registry.settings[\"sessions.secret\"],\n            config.registry.settings[\"sessions.url\"],\n        ),\n    )\n\n    config.add_view_deriver(\n        session_view,\n        over=\"csrf_view\",\n        under=viewderivers.INGRESS,\n    )\n", "path": "warehouse/sessions.py"}], "after_files": [{"content": "# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport functools\nimport time\n\nimport msgpack\nimport msgpack.exceptions\nimport redis\n\nfrom pyramid import viewderivers\nfrom pyramid.interfaces import ISession, ISessionFactory\nfrom zope.interface import implementer\n\nfrom warehouse.cache.http import add_vary\nfrom warehouse.utils import crypto\n\n\ndef _invalid_method(method):\n    @functools.wraps(method)\n    def wrapped(self, *args, **kwargs):\n        self._error_message()\n    return wrapped\n\n\n@implementer(ISession)\nclass InvalidSession(dict):\n\n    __contains__ = _invalid_method(dict.__contains__)\n    __delitem__ = _invalid_method(dict.__delitem__)\n    __getitem__ = _invalid_method(dict.__getitem__)\n    __iter__ = _invalid_method(dict.__iter__)\n    __len__ = _invalid_method(dict.__len__)\n    __setitem__ = _invalid_method(dict.__setitem__)\n    clear = _invalid_method(dict.clear)\n    copy = _invalid_method(dict.copy)\n    fromkeys = _invalid_method(dict.fromkeys)\n    get = _invalid_method(dict.get)\n    items = _invalid_method(dict.items)\n    keys = _invalid_method(dict.keys)\n    pop = _invalid_method(dict.pop)\n    popitem = _invalid_method(dict.popitem)\n    setdefault = _invalid_method(dict.setdefault)\n    update = _invalid_method(dict.update)\n    values = _invalid_method(dict.values)\n\n    def _error_message(self):\n        raise RuntimeError(\n            \"Cannot use request.session in a view without uses_session=True.\"\n        )\n\n    def __getattr__(self, name):\n        self._error_message()\n\n    @property\n    def created(self):\n        self._error_message()\n\n\ndef _changed_method(method):\n    @functools.wraps(method)\n    def wrapped(self, *args, **kwargs):\n        self.changed()\n        return method(self, *args, **kwargs)\n    return wrapped\n\n\n@implementer(ISession)\nclass Session(dict):\n\n    _csrf_token_key = \"_csrf_token\"\n    _flash_key = \"_flash_messages\"\n\n    # A number of our methods need to be decorated so that they also call\n    # self.changed()\n    __delitem__ = _changed_method(dict.__delitem__)\n    __setitem__ = _changed_method(dict.__setitem__)\n    clear = _changed_method(dict.clear)\n    pop = _changed_method(dict.pop)\n    popitem = _changed_method(dict.popitem)\n    setdefault = _changed_method(dict.setdefault)\n    update = _changed_method(dict.update)\n\n    def __init__(self, data=None, session_id=None, new=True):\n        # Brand new sessions don't have any data, so we'll just create an empty\n        # dictionary for them.\n        if data is None:\n            data = {}\n\n        # Initialize our actual dictionary here.\n        super().__init__(data)\n\n        # We need to track the state of our Session.\n        self._sid = session_id\n        self._changed = False\n        self.new = new\n        self.created = int(time.time())\n\n        # We'll track all of the IDs that have been invalidated here\n        self.invalidated = set()\n\n    @property\n    def sid(self):\n        if self._sid is None:\n            self._sid = crypto.random_token()\n        return self._sid\n\n    def changed(self):\n        self._changed = True\n\n    def invalidate(self):\n        self.clear()\n        self.new = True\n        self.created = int(time.time())\n        self._changed = False\n\n        # If the current session id isn't None we'll want to record it as one\n        # of the ones that have been invalidated.\n        if self._sid is not None:\n            self.invalidated.add(self._sid)\n            self._sid = None\n\n    def should_save(self):\n        return self._changed\n\n    # Flash Messages Methods\n    def _get_flash_queue_key(self, queue):\n        return \".\".join(filter(None, [self._flash_key, queue]))\n\n    def flash(self, msg, queue=\"\", allow_duplicate=True):\n        queue_key = self._get_flash_queue_key(queue)\n\n        # If we're not allowing duplicates check if this message is already\n        # in the queue, and if it is just return immediately.\n        if not allow_duplicate and msg in self[queue_key]:\n            return\n\n        self.setdefault(queue_key, []).append(msg)\n\n    def peek_flash(self, queue=\"\"):\n        return self.get(self._get_flash_queue_key(queue), [])\n\n    def pop_flash(self, queue=\"\"):\n        queue_key = self._get_flash_queue_key(queue)\n        messages = self.get(queue_key, [])\n        self.pop(queue_key, None)\n        return messages\n\n    # CSRF Methods\n    def new_csrf_token(self):\n        self[self._csrf_token_key] = crypto.random_token()\n        return self[self._csrf_token_key]\n\n    def get_csrf_token(self):\n        token = self.get(self._csrf_token_key)\n        if token is None:\n            token = self.new_csrf_token()\n        return token\n\n\n@implementer(ISessionFactory)\nclass SessionFactory:\n\n    cookie_name = \"session_id\"\n    max_age = 12 * 60 * 60  # 12 hours\n\n    def __init__(self, secret, url):\n        self.redis = redis.StrictRedis.from_url(url)\n        self.signer = crypto.TimestampSigner(secret, salt=\"session\")\n\n    def __call__(self, request):\n        return self._process_request(request)\n\n    def _redis_key(self, session_id):\n        return \"warehouse/session/data/{}\".format(session_id)\n\n    def _process_request(self, request):\n        # Register a callback with the request so we can save the session once\n        # it's finished.\n        request.add_response_callback(self._process_response)\n\n        # Load our session ID from the request.\n        session_id = request.cookies.get(self.cookie_name)\n\n        # If we do not have a session ID then we'll just use a new empty\n        # session.\n        if session_id is None:\n            return Session()\n\n        # Check to make sure we have a valid session id\n        try:\n            session_id = self.signer.unsign(session_id, max_age=self.max_age)\n            session_id = session_id.decode(\"utf8\")\n        except crypto.BadSignature:\n            return Session()\n\n        # Fetch the serialized data from redis\n        bdata = self.redis.get(self._redis_key(session_id))\n\n        # If the session didn't exist in redis, we'll give the user a new\n        # session.\n        if bdata is None:\n            return Session()\n\n        # De-serialize our session data\n        try:\n            data = msgpack.unpackb(bdata, encoding=\"utf8\", use_list=True)\n        except (msgpack.exceptions.UnpackException,\n                msgpack.exceptions.ExtraData):\n            # If the session data was invalid we'll give the user a new session\n            return Session()\n\n        # If we were able to load existing session data, load it into a\n        # Session class\n        session = Session(data, session_id, False)\n\n        return session\n\n    def _process_response(self, request, response):\n        # If the request has an InvalidSession, then the view can't have\n        # accessed the session, and we can just skip all of this anyways.\n        if isinstance(request.session, InvalidSession):\n            return\n\n        # Check to see if the session has been marked to be deleted, if it has\n        # benn then we'll delete it, and tell our response to delete the\n        # session cookie as well.\n        if request.session.invalidated:\n            for session_id in request.session.invalidated:\n                self.redis.delete(self._redis_key(session_id))\n\n            if not request.session.should_save():\n                response.delete_cookie(self.cookie_name)\n\n        # Check to see if the session has been marked to be saved, generally\n        # this means that the session data has been modified and thus we need\n        # to store the new data.\n        if request.session.should_save():\n            # Save our session in Redis\n            self.redis.setex(\n                self._redis_key(request.session.sid),\n                self.max_age,\n                msgpack.packb(\n                    request.session,\n                    encoding=\"utf8\",\n                    use_bin_type=True,\n                ),\n            )\n\n            # Send our session cookie to the client\n            response.set_cookie(\n                self.cookie_name,\n                self.signer.sign(request.session.sid.encode(\"utf8\")),\n                max_age=self.max_age,\n                httponly=True,\n                secure=request.scheme == \"https\",\n                samesite=b\"lax\"\n            )\n\n\ndef session_view(view, info):\n    if info.options.get(\"uses_session\"):\n        # If we're using the session, then we'll just return the original view\n        # with a small wrapper around it to ensure that it has a Vary: Cookie\n        # header.\n        return add_vary(\"Cookie\")(view)\n    elif info.exception_only:\n        return view\n    else:\n        # If we're not using the session on this view, then we'll wrap the view\n        # with a wrapper that just ensures that the session cannot be used.\n        @functools.wraps(view)\n        def wrapped(context, request):\n            # This whole method is a little bit of an odd duck, we want to make\n            # sure that we don't actually *access* request.session, because\n            # doing so triggers the machinery to create a new session. So\n            # instead we will dig into the request object __dict__ to\n            # effectively do the same thing, jsut without triggering an access\n            # on request.session.\n\n            # Save the original session so that we can restore it once the\n            # inner views have been called.\n            nothing = object()\n            original_session = request.__dict__.get(\"session\", nothing)\n\n            # This particular view hasn't been set to allow access to the\n            # session, so we'll just assign an InvalidSession to\n            # request.session\n            request.__dict__[\"session\"] = InvalidSession()\n\n            try:\n                # Invoke the real view\n                return view(context, request)\n            finally:\n                # Restore the original session so that things like\n                # pyramid_debugtoolbar can access it.\n                if original_session is nothing:\n                    del request.__dict__[\"session\"]\n                else:\n                    request.__dict__[\"session\"] = original_session\n\n        return wrapped\n\n\nsession_view.options = {\"uses_session\"}\n\n\ndef includeme(config):\n    config.set_session_factory(\n        SessionFactory(\n            config.registry.settings[\"sessions.secret\"],\n            config.registry.settings[\"sessions.url\"],\n        ),\n    )\n\n    config.add_view_deriver(\n        session_view,\n        over=\"csrf_view\",\n        under=viewderivers.INGRESS,\n    )\n", "path": "warehouse/sessions.py"}]}