SAML-Toolkits / python-saml

Python SAML Toolkit
MIT License
663 stars 306 forks source link

Convert SAML Redirect response to POST response #308

Closed imatrycode closed 1 year ago

imatrycode commented 1 year ago

Hi ,

Trying to use this toolkit. I am using authentik for my idp. I have a react app which uses fastapi as the backen. I am trying to implement saml however if I send a POST to the fastapi endpoint I cannot forward on the lookup data for the user after validating as react will not accept post.

I then switched to redirect response but this doesnt seem to be supported. I have opted to use redirect response on the ACS Url to redirect to my react app. My React app will then parse the saml response from the URL parameter and post it to my API.

The problem is the formatting / limitation of the redirect response it will not be parsed by your toolkit.

I can manually decode and inflate etc python side but I would like to use your library to validate the response.

Is there any way this toolkit can convert a redirect response recieved as post to a format which your toolkit can validate.

this is what I have

@router.post('/saml/callback')
async def saml_login_callback(relayed_saml_response: schemas.SamlCallback,request: Request, db: Session = Depends(db_depends.get_db)):
    saml_response = relayed_saml_response.SAMLResponse
    pysaml = SAMLParser(saml_settings)
    xml = pysaml.validate_saml_response(saml_response)
    encoded_xml = str(OneLogin_Saml2_Utils.decode_base64_and_inflate(saml_response))
    encoded_xml = OneLogin_Saml2_Utils.deflate_and_base64_encode(encoded_xml)
    req = {
        "http_host": request.client.host,
        "server_port": request.url.port,
        "script_name": request.url.path,
        "post_data": {"SAMLResponse": str(encoded_xml) },
        "get_data": { }
        # Advanced request options
        # "https": "",
        # "request_uri": "",
        # "query_string": "",
        # "validate_signature_from_qs": False,
        # "lowercase_urlencoding": False
    }
    auth = OneLogin_Saml2_Auth(req, saml_settings)
    auth.process_response()
    return 0

And I get this response

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/dist-packages/uvicorn/protocols/http/h11_impl.py", line 389, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/usr/local/lib/python3.10/dist-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/dist-packages/uvicorn/middleware/debug.py", line 81, in __call__
    raise exc from None
  File "/usr/local/lib/python3.10/dist-packages/uvicorn/middleware/debug.py", line 78, in __call__
    await self.app(scope, receive, inner_send)
  File "/usr/local/lib/python3.10/dist-packages/fastapi/applications.py", line 208, in __call__
    await super().__call__(scope, receive, send)
  File "/usr/local/lib/python3.10/dist-packages/starlette/applications.py", line 112, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/usr/local/lib/python3.10/dist-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/usr/local/lib/python3.10/dist-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/usr/local/lib/python3.10/dist-packages/starlette/middleware/cors.py", line 86, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "/usr/local/lib/python3.10/dist-packages/starlette/middleware/cors.py", line 142, in simple_response
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/dist-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/usr/local/lib/python3.10/dist-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/usr/local/lib/python3.10/dist-packages/starlette/routing.py", line 580, in __call__
    await route.handle(scope, receive, send)
  File "/usr/local/lib/python3.10/dist-packages/starlette/routing.py", line 241, in handle
    await self.app(scope, receive, send)
  File "/usr/local/lib/python3.10/dist-packages/starlette/routing.py", line 52, in app
    response = await func(request)
  File "/usr/local/lib/python3.10/dist-packages/fastapi/routing.py", line 219, in app
    raw_response = await run_endpoint_function(
  File "/usr/local/lib/python3.10/dist-packages/fastapi/routing.py", line 152, in run_endpoint_function
    return await dependant.call(**values)
  File "/mnt/c/Users/David.Brierley/OneDrive - GCI Network Solutions/Pictures/ossapi/nasstar-oss-api/./app/v1/auth/views.py", line 316, in saml_login_callback
    auth.process_response()
  File "/usr/local/lib/python3.10/dist-packages/onelogin/saml2/auth.py", line 128, in process_response
    response = self.response_class(self._settings, self._request_data['post_data']['SAMLResponse'])
  File "/usr/local/lib/python3.10/dist-packages/onelogin/saml2/response.py", line 37, in __init__
    self.document = OneLogin_Saml2_XML.to_etree(self.response)
  File "/usr/local/lib/python3.10/dist-packages/onelogin/saml2/xml_utils.py", line 64, in to_etree
    return OneLogin_Saml2_XML._parse_etree(xml, forbid_dtd=True, forbid_entities=True)
  File "/usr/local/lib/python3.10/dist-packages/onelogin/saml2/xmlparser.py", line 174, in fromstring
    rootelement = _etree.fromstring(text, parser, base_url=base_url)
  File "src/lxml/etree.pyx", line 3257, in lxml.etree.fromstring
  File "src/lxml/parser.pxi", line 1916, in lxml.etree._parseMemoryDocument
  File "src/lxml/parser.pxi", line 1803, in lxml.etree._parseDoc
  File "src/lxml/parser.pxi", line 1144, in lxml.etree._BaseParser._parseDoc
  File "src/lxml/parser.pxi", line 618, in lxml.etree._ParserContext._handleParseResultDoc
  File "src/lxml/parser.pxi", line 728, in lxml.etree._handleParseResult
  File "src/lxml/parser.pxi", line 657, in lxml.etree._raiseParseError
  File "<string>", line 1
lxml.etree.XMLSyntaxError: Start tag expected, '<' not found, line 1, column 1
pitbulk commented 1 year ago

why are you mixing pysaml and python-saml?

xml = pysaml.validate_saml_response(saml_response)
encoded_xml = str(OneLogin_Saml2_Utils.decode_base64_and_inflate(saml_response))

ACS URL should receive a POST and not a Redirect, this is how 99% of IdPs will expect such endpoint.