diff --git a/oidc_provider/lib/endpoints/authorize.py b/oidc_provider/lib/endpoints/authorize.py index 598d688..a050cd7 100644 --- a/oidc_provider/lib/endpoints/authorize.py +++ b/oidc_provider/lib/endpoints/authorize.py @@ -207,7 +207,7 @@ class AuthorizeEndpoint(object): query_fragment['session_state'] = session_state except Exception as error: - logger.debug('[Authorize] Error when trying to create response uri: %s', error) + logger.exception('[Authorize] Error when trying to create response uri: %s', error) raise AuthorizeError(self.params['redirect_uri'], 'server_error', self.grant_type) uri = uri._replace(query=urlencode(query_params, doseq=True), fragment=uri.fragment + urlencode(query_fragment, doseq=True)) diff --git a/oidc_provider/lib/utils/common.py b/oidc_provider/lib/utils/common.py index b7a6676..52bb962 100644 --- a/oidc_provider/lib/utils/common.py +++ b/oidc_provider/lib/utils/common.py @@ -84,6 +84,33 @@ def default_after_userlogin_hook(request, user, client): """ return None + +def default_post_end_session_hook(request, id_token=None, post_logout_redirect_uri=None, state=None, client=None, next_page=None): + """ + Default function for setting OIDC_POST_END_SESSION_HOOK. + + :param request: Django request object + :type request: django.http.HttpRequest + + :param id_token: token passed by `id_token_hint` url query param - do NOT trust this param or validate token + :type id_token: str + + :param post_logout_redirect_uri: redirect url from url query param - do NOT trust this param + :type post_logout_redirect_uri: str + + :param state: state param from url query params + :type state: str + + :param client: If id_token has `aud` param and associated Client exists, this is an instance of it - do NOT trust this param + :type client: oidc_provider.models.Client + + :param next_page: calculated next_page redirection target + :type next_page: str + :return: + """ + return None + + def default_idtoken_processing_hook(id_token, user): """ Hook to perform some additional actions ti `id_token` dictionary just before serialization. diff --git a/oidc_provider/settings.py b/oidc_provider/settings.py index 610534c..20f2f78 100644 --- a/oidc_provider/settings.py +++ b/oidc_provider/settings.py @@ -30,6 +30,14 @@ class DefaultSettings(object): """ return 'oidc_provider.lib.utils.common.default_after_userlogin_hook' + @property + def OIDC_POST_END_SESSION_HOOK(self): + """ + OPTIONAL. Provide a way to plug into the end session process just before calling + Django's logout function, typically to perform some business logic. + """ + return 'oidc_provider.lib.utils.common.default_post_end_session_hook' + @property def OIDC_CODE_EXPIRE(self): """ diff --git a/oidc_provider/tests/test_authorize_endpoint.py b/oidc_provider/tests/test_authorize_endpoint.py index 030d50b..80aab38 100644 --- a/oidc_provider/tests/test_authorize_endpoint.py +++ b/oidc_provider/tests/test_authorize_endpoint.py @@ -7,6 +7,7 @@ try: except ImportError: from urlparse import parse_qs, urlsplit import uuid +from mock import patch from django.contrib.auth.models import AnonymousUser from django.core.management import call_command @@ -26,6 +27,7 @@ from oidc_provider.tests.app.utils import ( is_code_valid, ) from oidc_provider.views import AuthorizeView +from oidc_provider.lib.endpoints.authorize import AuthorizeEndpoint class AuthorizeEndpointMixin(object): @@ -498,3 +500,40 @@ class AuthorizationHybridFlowTestCase(TestCase, AuthorizeEndpointMixin): response = self._auth_request('post', self.data, is_user_authenticated=True) self.assertIn('expires_in=36000', response['Location']) + + +class TestCreateResponseURI(TestCase): + def setUp(self): + url = reverse('oidc_provider:authorize') + user = create_fake_user() + client = create_fake_client(response_type='code', is_public=True) + + # Base data to create a uri response + data = { + 'client_id': client.client_id, + 'redirect_uri': client.default_redirect_uri, + 'response_type': client.response_type, + } + + factory = RequestFactory() + self.request = factory.post(url, data=data) + self.request.user = user + + @patch('oidc_provider.lib.endpoints.authorize.create_code') + @patch('oidc_provider.lib.endpoints.authorize.logger.exception') + def test_create_response_uri_logs_to_error(self, log_exception, create_code): + """ + A lot can go wrong when creating a response uri and this is caught with a general Exception error. The + information contained within this error should show up in the error log so production servers have something + to work with when things don't work as expected. + """ + exception = Exception("Something went wrong!") + create_code.side_effect = exception + + authorization_endpoint = AuthorizeEndpoint(self.request) + authorization_endpoint.validate_params() + + with self.assertRaises(Exception): + authorization_endpoint.create_response_uri() + + log_exception.assert_called_once_with('[Authorize] Error when trying to create response uri: %s', exception) diff --git a/oidc_provider/tests/test_end_session_endpoint.py b/oidc_provider/tests/test_end_session_endpoint.py index 46d9cc0..6982704 100644 --- a/oidc_provider/tests/test_end_session_endpoint.py +++ b/oidc_provider/tests/test_end_session_endpoint.py @@ -11,6 +11,7 @@ from oidc_provider.tests.app.utils import ( create_fake_client, create_fake_user, ) +import mock class EndSessionTestCase(TestCase): @@ -44,3 +45,10 @@ class EndSessionTestCase(TestCase): response = self.client.get(self.url, query_params) self.assertRedirects(response, self.LOGOUT_URL, fetch_redirect_response=False) + + @mock.patch(settings.get('OIDC_POST_END_SESSION_HOOK')) + def test_call_post_end_session_hook(self, hook_function): + self.client.get(self.url) + self.assertTrue(hook_function.called, 'OIDC_POST_END_SESSION_HOOK should be called') + self.assertTrue(hook_function.call_count == 1, 'OIDC_POST_END_SESSION_HOOK should be called once but was {}'.format(hook_function.call_count)) + diff --git a/oidc_provider/views.py b/oidc_provider/views.py index 6bf142f..ffc60c4 100644 --- a/oidc_provider/views.py +++ b/oidc_provider/views.py @@ -264,8 +264,10 @@ class EndSessionView(View): id_token_hint = request.GET.get('id_token_hint', '') post_logout_redirect_uri = request.GET.get('post_logout_redirect_uri', '') state = request.GET.get('state', '') + client = None next_page = settings.get('LOGIN_URL') + post_end_session_hook = settings.get('OIDC_POST_END_SESSION_HOOK', import_str=True) if id_token_hint: client_id = client_id_from_id_token(id_token_hint) @@ -283,6 +285,15 @@ class EndSessionView(View): except Client.DoesNotExist: pass + post_end_session_hook( + request=request, + id_token=id_token_hint, + post_logout_redirect_uri=post_logout_redirect_uri, + state=state, + client=client, + next_page=next_page + ) + return logout(request, next_page=next_page)