diff --git a/examples/sp.py b/examples/sp.py index 6409321..179d0a1 100755 --- a/examples/sp.py +++ b/examples/sp.py @@ -35,6 +35,11 @@ def get_default_login_return_url(self): 'sso_url': 'http://localhost:8000/saml/login/', 'slo_url': 'http://localhost:8000/saml/logout/', 'certificate': IDP_CERTIFICATE, + # for decrypting attributes with xmlsec1 + # 'encrypted_attributes': { + # 'xmlsec1_path': 'xmlsec1', + # 'sp_key_path': '../testd/keys/sample/sp-private-key.pem', + # }, }, }, ] diff --git a/flask_saml2/sp/idphandler.py b/flask_saml2/sp/idphandler.py index 50788ad..39c9c61 100644 --- a/flask_saml2/sp/idphandler.py +++ b/flask_saml2/sp/idphandler.py @@ -76,6 +76,7 @@ def __init__( sso_url: Optional[str] = None, slo_url: Optional[str] = None, certificate: Optional[X509] = None, + encrypted_attributes: Mapping[str, str] = None, **kwargs, ): """ @@ -99,11 +100,17 @@ def __init__( The ``sso_url``, ``slo_url``, and ``certificate`` can all be found in the IdP's metadata. + + ``encrypted_attributes`` is a map with extra configuration for decrypting + the saml EncryptedAttributes tag using xmlsec1. this requires installing + the xmlsec1 program to work. the map consists of ``xmlsec1_path`` and + ``sp_key_path``, which are paths to an xmlsec1 binary and your sp's key + for decrypting the attributes """ - super().__init__(**kwargs) self.sp = sp self.entity_id = entity_id + self.encrypted_attributes = encrypted_attributes if display_name is not None: self.display_name = display_name @@ -219,7 +226,8 @@ def get_response_parser(self, saml_response): """ return ResponseParser( self.decode_saml_string(saml_response), - certificate=self.certificate) + certificate=self.certificate, + encrypted_attributes=self.encrypted_attributes) def get_auth_data(self, response: ResponseParser) -> AuthData: """ diff --git a/flask_saml2/sp/parser.py b/flask_saml2/sp/parser.py index 2dd63a0..93580f8 100644 --- a/flask_saml2/sp/parser.py +++ b/flask_saml2/sp/parser.py @@ -1,3 +1,4 @@ +import subprocess from typing import Mapping, Optional from flask_saml2.types import XmlNode @@ -6,6 +7,16 @@ class ResponseParser(XmlParser): + def __init__(self, xml_string, *args, encrypted_attributes=None, **kwargs): + super().__init__(xml_string, *args, **kwargs) + if encrypted_attributes: + xml_string = subprocess.check_output([ + encrypted_attributes['xmlsec1_path'], + '--decrypt', + '--privkey-pem', encrypted_attributes['sp_key_path'], + '/dev/stdin'], input=xml_string) + self.xml_string = xml_string + self.xml_tree = self.parse_request(self.xml_string) def is_signed(self): sig = self.xml_tree.xpath('/samlp:Response/ds:Signature', namespaces=self.get_namespace_map()) @@ -36,7 +47,7 @@ def issue_instant(self) -> str: @cached_property def assertion(self) -> XmlNode: - return self._xpath_xml_tree('/samlp:Response/saml:Assertion')[0] + return self._xpath_xml_tree('.//saml:Assertion')[0] @cached_property def subject(self) -> XmlNode: