Skip to content

Instantly share code, notes, and snippets.

@ephes
Created July 19, 2024 12:28
Show Gist options
  • Save ephes/aecc9aeed0688bc09c86703b9a29f89c to your computer and use it in GitHub Desktop.
Save ephes/aecc9aeed0688bc09c86703b9a29f89c to your computer and use it in GitHub Desktop.
import base64
import pytest
from django.urls import reverse
from saml2 import server
from saml2.config import Config as PysamlConfig
@pytest.fixture(scope="session")
def idp_metadata_path(tmp_path_factory):
fn = tmp_path_factory.mktemp("data") / "idp.xml"
with fn.open("w") as f:
f.write('<EntityDescriptor entityID="https://localhost:8088/idp.xml" />')
return fn
SP_XML_TEMPLATE = """
<ns0:EntityDescriptor
xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="{entity_id}"
>
<ns0:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" />
</ns0:EntityDescriptor>
"""
class SamlResponse:
test_host = "http://testserver"
entity_id = f"{test_host}/sso/metadata/"
acs_path = reverse("django_saml2_auth:acs")
next_url = "/next-url/"
def __init__(self, username, idp_metadata_path):
self.username = username
self.idp_metadata_path = idp_metadata_path
@property
def acs_url(self):
return f"{self.test_host}{self.acs_path}"
@property
def identity_provider(self):
sp_metadata = SP_XML_TEMPLATE.format(test_host=self.test_host, entity_id=self.entity_id)
config_data = {
"entityid": "https://localhost:8088/idp.xml",
"service": {"idp": {}},
"metadata": {"inline": [sp_metadata]},
}
config = PysamlConfig().load(config_data)
idp = server.Server(config=config)
idp.ticket = {}
return idp
def create_authn_response(self, user_identity):
resp_args = {
"in_response_to": "id-some-id",
"sp_entity_id": self.entity_id,
"name_id_policy": None,
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
"destination": self.acs_url,
"authn": {"authn_auth": "http://www.example.com/authn"},
}
response = self.identity_provider.create_authn_response(user_identity, **resp_args)
encoded_response = base64.b64encode(str(response).encode("utf-8"))
return encoded_response
def modify_saml2_auth_settings(self, settings):
settings.SAML2_AUTH["ENTITY_ID"] = self.entity_id
settings.SAML2_AUTH["METADATA_LOCAL_FILE_PATH"] = self.idp_metadata_path
settings.SAML2_AUTH["DEFAULT_NEXT_URL"] = self.next_url
del settings.SAML2_AUTH["METADATA_AUTO_CONF_URL"]
@pytest.mark.django_db
def test_sso_signin(client, settings, idp_metadata_path, django_user_model):
# Given a SAML response with a username and an identity provider metadata file
username = "testuser"
saml_response = SamlResponse(username, idp_metadata_path)
# And the SAML2_AUTH settings are modified to use the SAML response's entity ID
saml_response.modify_saml2_auth_settings(settings)
# When the encoded SAML response is posted to the ACS URL
user_identity = {"uid": saml_response.username}
encoded_response = saml_response.create_authn_response(user_identity)
r = client.post(saml_response.acs_url, data={"SAMLResponse": [encoded_response]})
# Then the response is a redirect pointing to the DEFAULT_NEXT_URL
assert r.status_code == 302
assert r.url == saml_response.next_url
# And a user with the SAML response's username is created and can only authenticate via SSO
sso_user = django_user_model.objects.get(username=username)
assert sso_user.has_usable_password() is False
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment