I have implemented for our django back-end application (SP) possibility to login via SAML, as IDP im using Keycloak. It works fine, but I want to write tests to be sure that all logic is being executed correctly. For this I want to generate a post request with SAML as body and mock (unittest.mock.patch) the real request. But i stuck.
Here is my django view, which accepts get and post requests when I try to login via SAML:
class SamlLoginView(View):
    @staticmethod
    def prepare_django_request(request):
        if 'HTTP_X_FORWARDED_FOR' in request.META:
            server_port = 443
        else:
            server_port = request.META.get('SERVER_PORT')
        result = {
            'https': 'on' if request.is_secure() else 'off',
            'http_host': request.META['HTTP_HOST'],
            'script_name': request.META['PATH_INFO'],
            'server_port': server_port,
            'get_data': request.GET.copy(),
            'post_data': request.POST.copy(),
        }
        return result
    @never_cache
    def get(self, *args, **kwargs):
        req = SamlLoginView.prepare_django_request(self.request)
        auth = OneLogin_Saml2_Auth(req, settings.SAML_IDP_SETTINGS)
        return_url = self.request.GET.get('next') or settings.LOGIN_REDIRECT_URL
        return HttpResponseRedirect(auth.login(return_to=return_url))
    @never_cache
    def post(self, *args, **kwargs):
        req = SamlLoginView.prepare_django_request(self.request)
        print(req['post_data']['SAMLResponse'])
        auth = OneLogin_Saml2_Auth(req, settings.SAML_IDP_SETTINGS)
        auth.process_response()
        errors = auth.get_errors()
        if not errors:
            if auth.is_authenticated():
                logger.info("Login", extra={'action': 'login',
                                            'userid': auth.get_nameid()})
                user = authenticate(request=self.request,
                                    saml_authentication=auth)
                login(self.request, user)
                return HttpResponseRedirect("/")
            else:
                raise PermissionDenied()
        else:
            return HttpResponseBadRequest("Error when processing SAML Response: %s" % (', '.join(errors)))
In my tests, I wanted to directly call the post method, in which there will be a saml inside:
class TestSamlLogin(TestCase):
    def test_saml_auth(self, prepare):
        client = APIClient()
        url = reverse_lazy("miri_auth:samllogin")
        saml_resp='<xml with saml response>'
        resp = client.post(url, data=saml_resp)
but obviously it shows that request.POST is empty.
I then decided to make a mock for the prepare_django_request function, and manually insert the saml:
def mocked_prepare_request(request):
    post_query_dict = QueryDict(mutable=True)
    post_data = {
        'SAMLResponse': saml_xml,
        'RelayState': '/accounts/profile/'
    }
    post_query_dict.update(post_data)
    result = {
        'https': 'on',
        'http_host': '<http-host>',
        'script_name': '/api/auth/samllogin/',
        'server_port': '443',
        'get_data': {},
        'post_data': post_query_dict,
    }
    return result
class TestSamlLogin(TestCase):
        
   @patch('miri_auth.views.SamlLoginView.prepare_django_request', side_effect=mocked_prepare_request)
    def test_saml_auth(self, prepare):
        client = APIClient()
        url = reverse_lazy("miri_auth:samllogin")
        saml_resp='<xml with saml response>'
        resp = client.post(url, data=saml_resp)
and depending on how I pass the saml_xml it throws different errors, if i define it as string:
with open(os.path.join(TEST_FILES_PATH, 'saml.xml')) as f:
        saml_xml = " ".join([x.strip() for x in f])
it returns: lxml.etree.XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1, although I checked the output from saml_xml in the xml validator and it says that the xml is valid.
When i try to parse the file into xml in advance, i get another error later,
libraries with which I tried to parse:
import xml.etree.ElementTree as ET
from xml.dom import minidom
from lxml import etree
tree = etree.parse(os.path.join(TEST_FILES_PATH, 'saml.xml'))
it returns:
TypeError: argument should be a bytes-like object or ASCII string, not '_ElementTree'
Debugging these errors didn't lead me to any solution.
If anyone has any thoughts on how this can be implemented (Mocking response with SAML), or where I made a mistake, I would be glad to hear.
Thank in advance
                        
I realized that the SAML Response must be encoded:
but now i am getting the following errors: