109 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			109 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""SAML Identity Provider Metadata Processor"""
 | 
						|
from typing import Iterator, Optional
 | 
						|
 | 
						|
from django.http import HttpRequest
 | 
						|
from django.shortcuts import reverse
 | 
						|
from lxml.etree import Element, SubElement, tostring  # nosec
 | 
						|
from signxml.util import strip_pem_header
 | 
						|
 | 
						|
from passbook.providers.saml.models import SAMLProvider
 | 
						|
from passbook.sources.saml.processors.constants import (
 | 
						|
    NS_MAP,
 | 
						|
    NS_SAML_METADATA,
 | 
						|
    NS_SIGNATURE,
 | 
						|
    SAML_BINDING_POST,
 | 
						|
    SAML_BINDING_REDIRECT,
 | 
						|
    SAML_NAME_ID_FORMAT_EMAIL,
 | 
						|
    SAML_NAME_ID_FORMAT_PERSISTENT,
 | 
						|
    SAML_NAME_ID_FORMAT_TRANSIENT,
 | 
						|
    SAML_NAME_ID_FORMAT_X509,
 | 
						|
)
 | 
						|
 | 
						|
 | 
						|
class MetadataProcessor:
 | 
						|
    """SAML Identity Provider Metadata Processor"""
 | 
						|
 | 
						|
    provider: SAMLProvider
 | 
						|
    http_request: HttpRequest
 | 
						|
 | 
						|
    def __init__(self, provider: SAMLProvider, request: HttpRequest):
 | 
						|
        self.provider = provider
 | 
						|
        self.http_request = request
 | 
						|
 | 
						|
    def get_signing_key_descriptor(self) -> Optional[Element]:
 | 
						|
        """Get Singing KeyDescriptor, if enabled for the provider"""
 | 
						|
        if self.provider.signing_kp:
 | 
						|
            key_descriptor = Element(f"{{{NS_SAML_METADATA}}}KeyDescriptor")
 | 
						|
            key_descriptor.attrib["use"] = "signing"
 | 
						|
            key_info = SubElement(key_descriptor, f"{{{NS_SIGNATURE}}}KeyInfo")
 | 
						|
            x509_data = SubElement(key_info, f"{{{NS_SIGNATURE}}}X509Data")
 | 
						|
            x509_certificate = SubElement(
 | 
						|
                x509_data, f"{{{NS_SIGNATURE}}}X509Certificate"
 | 
						|
            )
 | 
						|
            x509_certificate.text = strip_pem_header(
 | 
						|
                self.provider.signing_kp.certificate_data.replace("\r", "")
 | 
						|
            ).replace("\n", "")
 | 
						|
            return key_descriptor
 | 
						|
        return None
 | 
						|
 | 
						|
    def get_name_id_formats(self) -> Iterator[Element]:
 | 
						|
        """Get compatible NameID Formats"""
 | 
						|
        formats = [
 | 
						|
            SAML_NAME_ID_FORMAT_EMAIL,
 | 
						|
            SAML_NAME_ID_FORMAT_PERSISTENT,
 | 
						|
            SAML_NAME_ID_FORMAT_X509,
 | 
						|
            SAML_NAME_ID_FORMAT_TRANSIENT,
 | 
						|
        ]
 | 
						|
        for name_id_format in formats:
 | 
						|
            element = Element(f"{{{NS_SAML_METADATA}}}NameIDFormat")
 | 
						|
            element.text = name_id_format
 | 
						|
            yield element
 | 
						|
 | 
						|
    def get_bindings(self) -> Iterator[Element]:
 | 
						|
        """Get all Bindings supported"""
 | 
						|
        binding_url_map = {
 | 
						|
            SAML_BINDING_POST: self.http_request.build_absolute_uri(
 | 
						|
                reverse(
 | 
						|
                    "passbook_providers_saml:sso-post",
 | 
						|
                    kwargs={"application_slug": self.provider.application.slug},
 | 
						|
                )
 | 
						|
            ),
 | 
						|
            SAML_BINDING_REDIRECT: self.http_request.build_absolute_uri(
 | 
						|
                reverse(
 | 
						|
                    "passbook_providers_saml:sso-redirect",
 | 
						|
                    kwargs={"application_slug": self.provider.application.slug},
 | 
						|
                )
 | 
						|
            ),
 | 
						|
        }
 | 
						|
        for binding, url in binding_url_map.items():
 | 
						|
            element = Element(f"{{{NS_SAML_METADATA}}}SingleSignOnService")
 | 
						|
            element.attrib["Binding"] = binding
 | 
						|
            element.attrib["Location"] = url
 | 
						|
            yield element
 | 
						|
 | 
						|
    def build_entity_descriptor(self) -> str:
 | 
						|
        """Build full EntityDescriptor"""
 | 
						|
        entity_descriptor = Element(
 | 
						|
            f"{{{NS_SAML_METADATA}}}EntityDescriptor", nsmap=NS_MAP
 | 
						|
        )
 | 
						|
        entity_descriptor.attrib["entityID"] = self.provider.issuer
 | 
						|
 | 
						|
        idp_sso_descriptor = SubElement(
 | 
						|
            entity_descriptor, f"{{{NS_SAML_METADATA}}}IDPSSODescriptor"
 | 
						|
        )
 | 
						|
        idp_sso_descriptor.attrib[
 | 
						|
            "protocolSupportEnumeration"
 | 
						|
        ] = "urn:oasis:names:tc:SAML:2.0:protocol"
 | 
						|
 | 
						|
        signing_descriptor = self.get_signing_key_descriptor()
 | 
						|
        if signing_descriptor is not None:
 | 
						|
            idp_sso_descriptor.append(signing_descriptor)
 | 
						|
 | 
						|
        for name_id_format in self.get_name_id_formats():
 | 
						|
            idp_sso_descriptor.append(name_id_format)
 | 
						|
 | 
						|
        for binding in self.get_bindings():
 | 
						|
            idp_sso_descriptor.append(binding)
 | 
						|
 | 
						|
        return tostring(entity_descriptor).decode()
 |