Skip to content

Commit

Permalink
Handle unsolicited auth response (#90)
Browse files Browse the repository at this point in the history
Return an error if receiving an authentication response without an associated authentication request, i.e. missing initialised user session or state/nonce stored in the session.
  • Loading branch information
zamzterz authored Sep 2, 2020
1 parent d6b7655 commit 88ccf50
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 1 deletion.
12 changes: 11 additions & 1 deletion src/flask_pyoidc/flask_pyoidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,16 @@ def _handle_authentication_response(self):
return self._show_error_response(flask.session.pop('error'))
return 'Something went wrong.'

try:
session = UserSession(flask.session)
except UninitialisedSession:
return self._handle_error_response({'error': 'unsolicited_response', 'error_description': 'No initialised user session.'})

if 'state' not in flask.session:
return self._handle_error_response({'error': 'unsolicited_response', 'error_description': "No 'state' stored."})
elif 'nonce' not in flask.session:
return self._handle_error_response({'error': 'unsolicited_response', 'error_description': "No 'nonce' stored."})

if flask.session.pop('fragment_encoded_response', False):
return importlib_resources.read_binary('flask_pyoidc', 'parse_fragment.html').decode('utf-8')

Expand All @@ -135,7 +145,7 @@ def _handle_authentication_response(self):
else:
auth_resp = flask.request.args

client = self.clients[UserSession(flask.session).current_provider]
client = self.clients[session.current_provider]

authn_resp = client.parse_authentication_response(auth_resp)
logger.debug('received authentication response: %s', authn_resp.to_json())
Expand Down
51 changes: 51 additions & 0 deletions tests/test_flask_pyoidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,60 @@ def test_handle_authentication_response_POST(self):
assert session.access_token == access_token
assert response == '/test'

def test_handle_authentication_response_without_initialised_session(self):
authn = self.init_app()

with self.app.test_request_context('/redirect_uri?state=test-state&code=test'):
response = authn._handle_authentication_response()
assert response == 'Something went wrong with the authentication, please try to login again.'

# with error view configured, error object should be sent to it instead
error_view_mock = self.get_view_mock()
authn.error_view(error_view_mock)
result = authn._handle_authentication_response()
self.assert_view_mock(error_view_mock, result)
error_view_mock.assert_called_with(**{'error': 'unsolicited_response', 'error_description': 'No initialised user session.'})

def test_handle_authentication_response_without_stored_state(self):
authn = self.init_app()

with self.app.test_request_context('/redirect_uri?state=test-state&code=test'):
UserSession(flask.session, self.PROVIDER_NAME)
flask.session['destination'] = '/test'
flask.session['nonce'] = 'test_nonce'
response = authn._handle_authentication_response()
assert response == 'Something went wrong with the authentication, please try to login again.'

# with error view configured, error object should be sent to it instead
error_view_mock = self.get_view_mock()
authn.error_view(error_view_mock)
result = authn._handle_authentication_response()
self.assert_view_mock(error_view_mock, result)
error_view_mock.assert_called_with(**{'error': 'unsolicited_response', 'error_description': "No 'state' stored."})

def test_handle_authentication_response_without_stored_nonce(self):
authn = self.init_app()

with self.app.test_request_context('/redirect_uri?state=test-state&code=test'):
UserSession(flask.session, self.PROVIDER_NAME)
flask.session['destination'] = '/test'
flask.session['state'] = 'test_state'
response = authn._handle_authentication_response()
assert response == 'Something went wrong with the authentication, please try to login again.'

# with error view configured, error object should be sent to it instead
error_view_mock = self.get_view_mock()
authn.error_view(error_view_mock)
result = authn._handle_authentication_response()
self.assert_view_mock(error_view_mock, result)
error_view_mock.assert_called_with(**{'error': 'unsolicited_response', 'error_description': "No 'nonce' stored."})

def test_handle_authentication_response_fragment_encoded(self):
authn = self.init_app()
with self.app.test_request_context('/redirect_uri'):
UserSession(flask.session, self.PROVIDER_NAME)
flask.session['state'] = 'test_state'
flask.session['nonce'] = 'test_nonce'
flask.session['fragment_encoded_response'] = True
response = authn._handle_authentication_response()
assert response.startswith('<html>')
Expand Down

0 comments on commit 88ccf50

Please sign in to comment.