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