Skip to content

Commit 61c59b5

Browse files
panagiksasvetlov
authored andcommitted
Implement new_session functionality (aio-libs#282)
1 parent 0f921a1 commit 61c59b5

File tree

4 files changed

+96
-3
lines changed

4 files changed

+96
-3
lines changed

aiohttp_session/__init__.py

+19
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,22 @@ async def get_session(request):
120120
return session
121121

122122

123+
async def new_session(request):
124+
storage = request.get(STORAGE_KEY)
125+
if storage is None:
126+
raise RuntimeError(
127+
"Install aiohttp_session middleware "
128+
"in your aiohttp.web.Application")
129+
else:
130+
session = await storage.new_session()
131+
if not isinstance(session, Session):
132+
raise RuntimeError(
133+
"Installed {!r} storage should return session instance "
134+
"on .load_session() call, got {!r}.".format(storage, session))
135+
request[SESSION_KEY] = session
136+
return session
137+
138+
123139
def session_middleware(storage):
124140

125141
if not isinstance(storage, AbstractStorage):
@@ -198,6 +214,9 @@ def _get_session_data(self, session):
198214
data = {}
199215
return data
200216

217+
async def new_session(self):
218+
return Session(None, data=None, new=True, max_age=self.max_age)
219+
201220
@abc.abstractmethod
202221
async def load_session(self, request):
203222
pass

demo/login_required_example.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from cryptography import fernet
55
from aiohttp import web
6-
from aiohttp_session import setup, get_session
6+
from aiohttp_session import setup, get_session, new_session
77
from aiohttp_session.cookie_storage import EncryptedCookieStorage
88

99

@@ -61,7 +61,9 @@ async def login(request):
6161
# actually implement business logic to check user credentials
6262
try:
6363
user_id = DATABASE.index(user_signature)
64-
session = await get_session(request)
64+
# Always use `new_session` during login to guard against
65+
# Session Fixation. See aiohttp-session#281
66+
session = await new_session(request)
6567
session['user_id'] = user_id
6668
return web.HTTPFound(router['restricted'].url_for())
6769
except ValueError:

docs/reference.rst

+25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ Public functions
2121
See example below in :ref:`Session<aiohttp-session-session>`
2222
section for :func:`get_session` usage.
2323

24+
.. function:: new_session(request)
25+
26+
A :ref:`coroutine<coroutine>` for getting a new session regardless
27+
of whether a cookie exists.
28+
29+
.. warning::
30+
31+
Always use :func:`new_session` instead of :func:`get_session`
32+
in your login views to guard against Session Fixation attacks!
33+
34+
Example usage::
35+
36+
from aiohttp_session import new_session
37+
38+
async def handler(request):
39+
session = await new_session(request)
40+
session.new == True # This will always be True
41+
2442
.. function:: session_middleware(storage)
2543

2644
Session middleware factory.
@@ -214,6 +232,13 @@ implement both :meth:`~AbstractStorage.load_session` and
214232

215233
.. versionadded:: 2.3
216234

235+
.. method:: new_session()
236+
237+
A :ref:`coroutine<coroutine>` for getting a new session regardless
238+
of whether a cookie exists.
239+
240+
Return :class:`Session` instance.
241+
217242
.. method:: load_session(request)
218243

219244
An *abstract* :ref:`coroutine<coroutine>`, called by internal

tests/test_get_session.py

+48-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
from aiohttp.test_utils import make_mocked_request
44

5-
from aiohttp_session import Session, get_session, SESSION_KEY, STORAGE_KEY
5+
from aiohttp_session import (Session, get_session, SESSION_KEY, STORAGE_KEY,
6+
new_session, AbstractStorage)
67

78

89
async def test_get_stored_session():
@@ -32,3 +33,49 @@ async def load_session(self, request):
3233

3334
with pytest.raises(RuntimeError):
3435
await get_session(req)
36+
37+
38+
async def test_get_new_session():
39+
req = make_mocked_request('GET', '/')
40+
session = Session('identity', data=None, new=False)
41+
42+
class Storage(AbstractStorage):
43+
async def load_session(self, request):
44+
pass
45+
46+
async def save_session(self, request, response, session):
47+
pass
48+
49+
req[SESSION_KEY] = session
50+
req[STORAGE_KEY] = Storage()
51+
52+
ret = await new_session(req)
53+
assert ret is not session
54+
55+
56+
async def test_get_new_session_no_storage():
57+
req = make_mocked_request('GET', '/')
58+
session = Session('identity', data=None, new=False)
59+
req[SESSION_KEY] = session
60+
61+
with pytest.raises(RuntimeError):
62+
await new_session(req)
63+
64+
65+
async def test_get_new_session_bad_return():
66+
req = make_mocked_request('GET', '/')
67+
68+
class Storage(AbstractStorage):
69+
async def new_session(self):
70+
return ''
71+
72+
async def load_session(self, request):
73+
pass
74+
75+
async def save_session(self, request, response, session):
76+
pass
77+
78+
req[STORAGE_KEY] = Storage()
79+
80+
with pytest.raises(RuntimeError):
81+
await new_session(req)

0 commit comments

Comments
 (0)