From 88ccf50ae490a45807ecb969e8559eb75641c917 Mon Sep 17 00:00:00 2001 From: Samuel Gulliksson Date: Wed, 2 Sep 2020 09:11:27 +0200 Subject: [PATCH] Handle unsolicited auth response (#90) 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. --- src/flask_pyoidc/flask_pyoidc.py | 12 +++++++- tests/test_flask_pyoidc.py | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/flask_pyoidc/flask_pyoidc.py b/src/flask_pyoidc/flask_pyoidc.py index dbea724..5ca699f 100644 --- a/src/flask_pyoidc/flask_pyoidc.py +++ b/src/flask_pyoidc/flask_pyoidc.py @@ -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') @@ -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()) diff --git a/tests/test_flask_pyoidc.py b/tests/test_flask_pyoidc.py index 5e2f52d..427cdf9 100644 --- a/tests/test_flask_pyoidc.py +++ b/tests/test_flask_pyoidc.py @@ -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('')