使用python3-saml从onelogin.saml.auth库获取python django Mock SAML响应

问题描述

我为django后端应用程序(SP)实现了通过SAML登录的可能性,就像IDP im使用Keycloak一样。它工作正常,但我想编写测试以确保所有逻辑都正确执行。为此,我想生成一个以SAML作为正文的发布请求,并模拟真实请求(unittest.mock.patch)。但是我坚持住了。

这是我的django视图,当我尝试通过SAML登录时,它接受get和post请求:

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,**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)))

在测试中,我想直接调用post方法,其中将包含一个saml:

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)

但显然它表明request.POST为空。

然后,我决定对prepare_django_request函数进行模拟,并手动插入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,data=saml_resp)

,如果我将其定义为字符串,则根据我通过saml_xml的方式会引发不同的错误

with open(os.path.join(TEST_FILES_PATH,'saml.xml')) as f:
        saml_xml = " ".join([x.strip() for x in f])

它返回:lxml.etree.XMLSyntaxError: Start tag expected,'<' not found,line 1,column 1,尽管我在xml验证器中检查了saml_xml输出,并说xml是有效的。 当我尝试事先将文件解析为xml时,后来又收到另一个错误, 我尝试解析的库:

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'))

它返回: TypeError: argument should be a bytes-like object or ASCII string,not '_ElementTree'

调试这些错误并没有导致我找到任何解决方案。

如果有人对如何实现该功能(使用SAML模拟响应)或在哪里出错有任何想法,我将很高兴听到。

预先感谢

解决方法

我意识到必须对SAML响应进行编码:

with open(os.path.join(TEST_FILES_PATH,'saml.xml')) as f:
            saml_xml = " ".join([x.strip() for x in f])
            base64_saml = base64.b64encode(saml_xml.encode('ascii')).decode('ascii')
            post_data = {'SAMLResponse': base64_saml,'RelayState': '/accounts/profile/'}
            url = reverse_lazy("miri_auth:samllogin")
            request = self.client.post(url,post_data)

但是现在我遇到以下错误:

func=xmlSecOpenSSLEvpDigestVerify:file=digests.c:line=280:obj=sha256:subj=unknown:error=12:invalid data:data and digest do not match

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...