Skip to content

Commit

Permalink
Store ID Token claim 'auth_time' in user session if it's set. (#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
zamzterz authored Oct 6, 2018
1 parent 9803cbd commit 42f5580
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 18 deletions.
3 changes: 1 addition & 2 deletions src/flask_pyoidc/flask_pyoidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,7 @@ def _handle_authentication_response(self):
if id_token_claims and userinfo_claims and userinfo_claims['sub'] != id_token_claims['sub']:
raise ValueError('The \'sub\' of userinfo does not match \'sub\' of ID Token.')

UserSession(flask.session).update(time.time(),
access_token,
UserSession(flask.session).update(access_token,
id_token_claims,
token_resp.get('id_token_jwt'),
userinfo_claims)
Expand Down
13 changes: 10 additions & 3 deletions src/flask_pyoidc/user_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ def _refresh_time(self, refresh_interval_seconds):
last = self._session_storage.get('last_authenticated', 0)
return last + refresh_interval_seconds

def update(self, last_authenticated, access_token=None, id_token=None, id_token_jwt=None, userinfo=None):
def update(self, access_token=None, id_token=None, id_token_jwt=None, userinfo=None):
"""
Args:
last_authenticated (float)
access_token (str)
id_token (Mapping[str, str])
id_token_jwt (str)
Expand All @@ -56,7 +55,11 @@ def set_if_defined(session_key, value):
if value:
self._session_storage[session_key] = value

self._session_storage['last_authenticated'] = last_authenticated
auth_time = int(time.time())
if id_token:
auth_time = id_token.get('auth_time', auth_time)

self._session_storage['last_authenticated'] = auth_time
set_if_defined('access_token', access_token)
set_if_defined('id_token', id_token)
set_if_defined('id_token_jwt', id_token_jwt)
Expand Down Expand Up @@ -85,3 +88,7 @@ def userinfo(self):
@property
def current_provider(self):
return self._session_storage['current_provider']

@property
def last_authenticated(self):
return self._session_storage['last_authenticated']
15 changes: 8 additions & 7 deletions tests/test_flask_pyoidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,18 @@ def test_should_not_authenticate_if_session_exists(self):
authn = self.get_authn_instance()
view_mock = self.get_view_mock()
with self.app.test_request_context('/'):
UserSession(flask.session, self.PROVIDER_NAME).update(time.time())
UserSession(flask.session, self.PROVIDER_NAME).update()
result = authn.oidc_auth(self.PROVIDER_NAME)(view_mock)()
self.assert_view_mock(view_mock, result)

def test_reauthenticate_silent_if_session_expired(self):
authn = self.get_authn_instance(session_refresh_interval_seconds=1)
view_mock = self.get_view_mock()
with self.app.test_request_context('/'):
UserSession(flask.session, self.PROVIDER_NAME).update(time.time() - 1) # authenticated in the past
now = time.time()
with patch('time.time') as time_mock:
time_mock.return_value = now - 1 # authenticated in the past
UserSession(flask.session, self.PROVIDER_NAME).update()
auth_redirect = authn.oidc_auth(self.PROVIDER_NAME)(view_mock)()

self.assert_auth_redirect(auth_redirect)
Expand All @@ -101,7 +104,7 @@ def test_dont_reauthenticate_silent_if_session_not_expired(self):
authn = self.get_authn_instance(session_refresh_interval_seconds=999)
view_mock = self.get_view_mock()
with self.app.test_request_context('/'):
UserSession(flask.session, self.PROVIDER_NAME).update(time.time()) # freshly authenticated
UserSession(flask.session, self.PROVIDER_NAME).update() # freshly authenticated
result = authn.oidc_auth(self.PROVIDER_NAME)(view_mock)()
self.assert_view_mock(view_mock, result)

Expand Down Expand Up @@ -228,8 +231,7 @@ def test_logout_redirects_to_provider_if_end_session_endpoint_is_configured(self
self.app.add_url_rule('/logout', view_func=authn.oidc_logout(logout_view_mock))

with self.app.test_request_context('/logout'):
UserSession(flask.session, self.PROVIDER_NAME).update(time.time(),
'test_access_token',
UserSession(flask.session, self.PROVIDER_NAME).update('test_access_token',
id_token.to_dict(),
id_token.to_jwt(),
{'sub': 'user1'})
Expand All @@ -251,8 +253,7 @@ def test_logout_handles_provider_without_end_session_endpoint(self):
id_token = IdToken(**{'sub': 'sub1', 'nonce': 'nonce'})
logout_view_mock = self.get_view_mock()
with self.app.test_request_context('/logout'):
UserSession(flask.session, self.PROVIDER_NAME).update(time.time(),
'test_access_token',
UserSession(flask.session, self.PROVIDER_NAME).update('test_access_token',
id_token.to_dict(),
id_token.to_jwt(),
{'sub': 'user1'})
Expand Down
21 changes: 15 additions & 6 deletions tests/test_user_session.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import time

import pytest
from mock import patch

from flask_pyoidc.user_session import UserSession, UninitialisedSession

Expand All @@ -14,7 +15,7 @@ def initialised_session(self, session_storage):
def test_initialising_session_with_existing_user_session_should_preserve_state(self):
storage = {}
session1 = UserSession(storage, self.PROVIDER_NAME)
session1.update(time.time())
session1.update()
assert session1.is_authenticated() is True
assert session1.current_provider == self.PROVIDER_NAME

Expand All @@ -29,7 +30,7 @@ def test_initialising_session_with_existing_user_session_should_preserve_state(s
def test_initialising_session_with_new_provider_name_should_reset_session(self):
storage = {}
session1 = UserSession(storage, 'provider1')
session1.update(time.time())
session1.update()
assert session1.is_authenticated() is True
session2 = UserSession(storage, 'provider2')
assert session2.is_authenticated() is False
Expand Down Expand Up @@ -63,26 +64,34 @@ def test_should_refresh_if_supported_and_not_previously_authenticated(self):
{'id_token_jwt': 'eyJh.eyJz.SflK'},
{'userinfo': {'sub': 'user1', 'name': 'Test User'}},
])
def test_update(self, data):
@patch('time.time')
def test_update(self, time_mock, data):
storage = {}
auth_time = 1234
time_mock.return_value = auth_time

self.initialised_session(storage).update(auth_time, **data)
self.initialised_session(storage).update(**data)

expected_session_data = {'last_authenticated': auth_time, 'current_provider': self.PROVIDER_NAME}
expected_session_data.update(**data)
assert storage == expected_session_data

def test_update_should_use_auth_time_from_id_token_if_it_exists(self):
auth_time = 1234
session = self.initialised_session({})
session.update(id_token={'auth_time': auth_time})
assert session.last_authenticated == auth_time

def test_trying_to_update_uninitialised_session_should_throw_exception(self):
with pytest.raises(UninitialisedSession):
UserSession(session_storage={}).update(time.time())
UserSession(session_storage={}).update()

def test_clear(self):
expected_data = {'initial data': 'should remain'}
session_storage = expected_data.copy()

session = self.initialised_session(session_storage)
session.update(time.time(), 'access_token', {'sub': 'user1'}, 'eyJh.eyJz.SflK', {'sub': 'user1}'})
session.update('access_token', {'sub': 'user1'}, 'eyJh.eyJz.SflK', {'sub': 'user1}'})
session.clear()

assert session_storage == expected_data

0 comments on commit 42f5580

Please sign in to comment.