28 |
28 |
from django.conf import settings
|
29 |
29 |
from django.contrib import auth
|
30 |
30 |
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
|
|
31 |
from django.core import signing
|
31 |
32 |
from django.db import transaction
|
32 |
33 |
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseRedirect
|
33 |
34 |
from django.shortcuts import render, resolve_url
|
... | ... | |
620 |
621 |
|
621 |
622 |
class LogoutView(ProfileMixin, LogMixin, View):
|
622 |
623 |
def get(self, request, *args, logout_next_url='/', **kwargs):
|
623 |
|
if 'SAMLRequest' in request.GET:
|
|
624 |
if 'token' in request.GET:
|
|
625 |
return self.sp_logout_token(request, token=request.GET['token'], logout_next_url=logout_next_url)
|
|
626 |
elif 'SAMLRequest' in request.GET:
|
624 |
627 |
return self.idp_logout(request, request.META['QUERY_STRING'], 'redirect')
|
625 |
628 |
elif 'SAMLResponse' in request.GET:
|
626 |
629 |
return self.sp_logout_response(request, logout_next_url=logout_next_url)
|
... | ... | |
728 |
731 |
else:
|
729 |
732 |
return HttpResponseRedirect(logout.msgUrl)
|
730 |
733 |
|
|
734 |
def next_url_cookie_name(self, relaystate):
|
|
735 |
return f'MellonNextURL-{relaystate}'
|
|
736 |
|
731 |
737 |
def sp_logout_request(self, request, logout_next_url=None):
|
732 |
738 |
'''Launch a logout request to the identity provider'''
|
733 |
739 |
referer = request.headers.get('Referer')
|
... | ... | |
778 |
784 |
'''Launch a logout request to the identity provider'''
|
779 |
785 |
self.profile = logout = utils.create_logout(request)
|
780 |
786 |
logout.msgRelayState = request.GET.get('RelayState')
|
|
787 |
cookie_name = self.next_url_cookie_name(logout.msgRelayState)
|
|
788 |
cookie_next_url = request.COOKIES.get(cookie_name)
|
|
789 |
next_url = self.get_next_url() or cookie_next_url or logout_next_url
|
781 |
790 |
# the user shouldn't be logged anymore at this point but it may happen
|
782 |
791 |
# that a concurrent SSO happened in the meantime, so we do another
|
783 |
792 |
# logout to make sure.
|
... | ... | |
789 |
798 |
self.log.warning('partial logout')
|
790 |
799 |
except lasso.Error as e:
|
791 |
800 |
self.log.warning('unable to process a logout response: %s', e)
|
792 |
|
return HttpResponseRedirect(self.get_next_url() or logout_next_url)
|
|
801 |
response = HttpResponseRedirect(next_url)
|
|
802 |
if cookie_name in request.COOKIES:
|
|
803 |
response.delete_cookie(cookie_name)
|
|
804 |
return response
|
|
805 |
|
|
806 |
TOKEN_SALT = 'mellon-logout-token'
|
|
807 |
|
|
808 |
def sp_logout_token(self, request, token, logout_next_url):
|
|
809 |
token_content = signing.loads(token, salt=self.TOKEN_SALT)
|
|
810 |
next_url = token_content['next_url'] or logout_next_url
|
|
811 |
session_index_pk = token_content['session_index_pk']
|
|
812 |
session_indexes = models.SessionIndex.objects.filter(pk=session_index_pk)
|
|
813 |
if session_indexes:
|
|
814 |
session_dump = utils.make_session_dump(session_indexes)
|
|
815 |
logout = utils.create_logout(request)
|
|
816 |
logout.msgRelayState = str(uuid.uuid4())
|
|
817 |
try:
|
|
818 |
logout.setSessionFromDump(session_dump)
|
|
819 |
logout.initRequest(
|
|
820 |
session_indexes[0].saml_identifier.issuer.entity_id, lasso.HTTP_METHOD_REDIRECT
|
|
821 |
)
|
|
822 |
logout.buildRequestMsg()
|
|
823 |
except lasso.Error as e:
|
|
824 |
self.log.error('unable to initiate a logout request %r', e)
|
|
825 |
return HttpResponseRedirect(next_url)
|
|
826 |
except Exception:
|
|
827 |
self.log.exception('unable to initiate a logout request')
|
|
828 |
return HttpResponseRedirect(next_url)
|
|
829 |
else:
|
|
830 |
self.log.debug('sending LogoutRequest %r to URL %r', logout.request.dump(), logout.msgUrl)
|
|
831 |
response = HttpResponseRedirect(logout.msgUrl)
|
|
832 |
response.set_cookie(
|
|
833 |
self.next_url_cookie_name(logout.msgRelayState),
|
|
834 |
value=next_url,
|
|
835 |
max_age=600,
|
|
836 |
samesite='Lax',
|
|
837 |
)
|
|
838 |
return response
|
|
839 |
return HttpResponseRedirect(next_url)
|
|
840 |
|
|
841 |
@classmethod
|
|
842 |
def make_logout_token_url(cls, request, next_url=None):
|
|
843 |
issuer = request.session.get('mellon_session', {}).get('issuer')
|
|
844 |
if not issuer:
|
|
845 |
return None
|
|
846 |
session_indexes = models.SessionIndex.objects.filter(
|
|
847 |
saml_identifier__user=request.user, saml_identifier__issuer__entity_id=issuer
|
|
848 |
).order_by('-id')[:1]
|
|
849 |
if not session_indexes:
|
|
850 |
return None
|
|
851 |
|
|
852 |
token_content = {
|
|
853 |
'next_url': next_url,
|
|
854 |
'session_index_pk': session_indexes[0].pk,
|
|
855 |
}
|
|
856 |
token = signing.dumps(token_content, salt=cls.TOKEN_SALT)
|
|
857 |
return reverse('mellon_logout') + '?' + urlencode({'token': token})
|
793 |
858 |
|
794 |
859 |
|
795 |
860 |
logout = csrf_exempt(LogoutView.as_view())
|