This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/passbook/sources/saml/utils.py

84 lines
2.8 KiB
Python

"""saml sp helpers"""
from django.http import HttpRequest
from django.shortcuts import reverse
from passbook.core.models import User
from passbook.sources.saml.models import SAMLSource
def get_entity_id(request: HttpRequest, source: SAMLSource):
"""Get Source's entity ID, falling back to our Metadata URL if none is set"""
entity_id = source.entity_id
if entity_id is None:
return build_full_url("metadata", request, source)
return entity_id
def build_full_url(view: str, request: HttpRequest, source: SAMLSource) -> str:
"""Build Full ACS URL to be used in IDP"""
return request.build_absolute_uri(
reverse(f"passbook_sources_saml:{view}", kwargs={"source": source.slug})
)
def _get_email_from_response(root):
"""
Returns the email out of the response.
At present, response must pass the email address as the Subject, eg.:
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:email"
SPNameQualifier=""
>email@example.com</saml:NameID>
"""
assertion = root.find("{urn:oasis:names:tc:SAML:2.0:assertion}Assertion")
subject = assertion.find("{urn:oasis:names:tc:SAML:2.0:assertion}Subject")
name_id = subject.find("{urn:oasis:names:tc:SAML:2.0:assertion}NameID")
return name_id.text
def _get_attributes_from_response(root):
"""
Returns the SAML Attributes (if any) that are present in the response.
NOTE: Technically, attribute values could be any XML structure.
But for now, just assume a single string value.
"""
flat_attributes = {}
assertion = root.find("{urn:oasis:names:tc:SAML:2.0:assertion}Assertion")
attributes = assertion.find(
"{urn:oasis:names:tc:SAML:2.0:assertion}AttributeStatement"
)
for attribute in attributes.getchildren():
name = attribute.attrib.get("Name")
children = attribute.getchildren()
if not children:
# Ignore empty-valued attributes. (I think these are not allowed.)
continue
if len(children) == 1:
# See NOTE:
flat_attributes[name] = children[0].text
else:
# It has multiple values.
for child in children:
# See NOTE:
flat_attributes.setdefault(name, []).append(child.text)
return flat_attributes
def _get_user_from_response(root):
"""
Gets info out of the response and locally logs in this user.
May create a local user account first.
Returns the user object that was created.
"""
email = _get_email_from_response(root)
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
user = User.objects.create_user(username=email, email=email)
user.set_unusable_password()
user.save()
return user