def test_verify_response_nia(self): with cast(TextIO, (DATA_DIR / 'nia_test_response.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) response = SAMLResponse(tree) response.verify_response(NIA_CERT_FILE)
def test_verify_assertion_nia_not_decrypted(self): with cast(TextIO, (DATA_DIR / 'nia_test_response.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) response = SAMLResponse(tree) self.assertFalse(response.verify_assertion(NIA_CERT_FILE))
def test_encrypt_xml_node(self): supported_ciphers = set(XmlBlockCipher) # type: Set[XmlBlockCipher] if LIBXMLSEC_VERSION < (1, 2, 27): # pragma: no cover supported_ciphers -= { XmlBlockCipher.AES128_GCM, XmlBlockCipher.AES192_GCM, XmlBlockCipher.AES256_GCM } for cipher in supported_ciphers: with cast(BinaryIO, (DATA_DIR / 'saml_response_decrypted.xml').open('rb')) as f: document = parse_xml(f.read()) remove_extra_xml_whitespace(document.getroot()) original = dump_xml(document).decode() # Encrypt <Assertion> assertion = document.find(".//{}".format( Q_NAMES['saml2:Assertion'])) encrypt_xml_node(assertion, CERT_FILE, cipher, XmlKeyTransport.RSA_OAEP_MGF1P) # <Assertion> replaced with <EncryptedData> self.assertIsNone( document.find(".//{}".format(Q_NAMES['saml2:Assertion']))) enc_data = document.find(".//{}/{}".format( Q_NAMES['saml2:EncryptedAssertion'], Q_NAMES['xmlenc:EncryptedData'])) self.assertIsNotNone(enc_data) self.assertEqual(enc_data[0].get('Algorithm'), cipher.value) # Verify that the original and decrypted document match. self.assertEqual(decrypt_xml(document, KEY_FILE), 1) decrypted = dump_xml(document).decode() self.assertEqual(original, decrypted)
def test_verify_xml_signatures_success(self): with cast(TextIO, (DATA_DIR / 'signed_response.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace( tree) # Reverts pretty printing applied after signing verify_xml_signatures(tree, CERT_FILE)
def test_verify_response_without_assertion(self): with cast(TextIO, (DATA_DIR / 'signed_failed_response.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) response = SAMLResponse(tree) response.verify_response(CERT_FILE)
def test_get_saml_response_invalid_signature(self): with cast(TextIO, (DATA_DIR / 'signed_response_and_assertion.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) saml_response_encoded = b64encode(dump_xml(tree, pretty_print=False)).decode('ascii') view = IdentityProviderResponseView() view.request = self.factory.post(self.url, {'SAMLResponse': saml_response_encoded}) self.assertRaises(SecurityError, view.get_saml_response, None, WRONG_CERT_FILE)
def load_saml_request(self, signed=False) -> Tuple[str, str]: path = 'saml_request.xml' if not signed else 'saml_request_signed.xml' with cast(BinaryIO, (DATA_DIR / path).open('rb')) as f: saml_request_pretty = f.read() saml_request = parse_xml(saml_request_pretty) remove_extra_xml_whitespace(saml_request) saml_request_encoded = b64encode( dump_xml(saml_request, pretty_print=False)) return saml_request_pretty.decode( 'utf-8'), saml_request_encoded.decode('ascii')
def test_remove_extra_xml_whitespace_text_preserved(self): for text in ' abc', 'abc': with self.subTest(text=text): root, child, grandchild = self.create_tree(text) remove_extra_xml_whitespace(root) self.assertEqual(root.text, text) self.assertEqual(root.tail, text) self.assertEqual(child.text, text) self.assertEqual(child.tail, text) self.assertEqual(grandchild.text, text) self.assertEqual(grandchild.tail, text)
def test_remove_extra_xml_whitespace_leaf_node_text_empty(self): for text in None, '': with self.subTest(text=text): root, child, grandchild = self.create_tree(' ') grandchild.text = text remove_extra_xml_whitespace(root) self.assertIsNone(root.text) self.assertIsNone(root.tail) self.assertIsNone(child.text) self.assertIsNone(child.tail) self.assertIsNone(grandchild.text) self.assertIsNone(grandchild.tail)
def test_remove_extra_xml_whitespace_various_whitespace(self): for space in None, '', ' ', ' \n\t': with self.subTest(space=space): root, child, grandchild = self.create_tree(space) grandchild.text = None remove_extra_xml_whitespace(root) self.assertIsNone(root.text) self.assertIsNone(root.tail) self.assertIsNone(child.text) self.assertIsNone(child.tail) self.assertIsNone(grandchild.text) self.assertIsNone(grandchild.tail)
def test_get_saml_response_signed_and_encrypted(self): with cast(TextIO, (DATA_DIR / 'nia_test_response.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) saml_response_encoded = b64encode(dump_xml(tree, pretty_print=False)).decode('ascii') view = IdentityProviderResponseView() view.request = self.factory.post(self.url, {'SAMLResponse': saml_response_encoded, 'RelayState': 'relay123'}) saml_response = view.get_saml_response(KEY_FILE, NIA_CERT_FILE) self.assertEqual(saml_response.relay_state, 'relay123') with cast(TextIO, (DATA_DIR / 'nia_test_response_decrypted_verified.xml').open('r')) as f: decrypted_verified_xml = f.read() self.assertXMLEqual(dump_xml(saml_response.document).decode('utf-8'), decrypted_verified_xml)
def test_remove_extra_xml_whitespace_leaf_node_whitespace_text_preserved( self): for text in ' ', ' \t\n': with self.subTest(text=text): root, child, grandchild = self.create_tree(' ') grandchild.text = text remove_extra_xml_whitespace(root) self.assertIsNone(root.text) self.assertIsNone(root.tail) self.assertIsNone(child.text) self.assertIsNone(child.tail) self.assertEqual(grandchild.text, text) self.assertIsNone(grandchild.tail)
def test_get_saml_response_signed(self): with cast(TextIO, (DATA_DIR / 'signed_response_and_assertion.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) saml_response_encoded = b64encode(dump_xml(tree, pretty_print=False)).decode('ascii') view = IdentityProviderResponseView() view.request = self.factory.post(self.url, {'SAMLResponse': saml_response_encoded, 'RelayState': 'relay123'}) saml_response = view.get_saml_response(None, CERT_FILE) self.assertEqual(saml_response.relay_state, 'relay123') root = Element(Q_NAMES['saml2p:Response'], {'ID': 'id-response'}, nsmap={'saml2': EIDAS_NAMESPACES['saml2'], 'saml2p': EIDAS_NAMESPACES['saml2p']}) assertion = SubElement(root, Q_NAMES['saml2:Assertion'], {'ID': 'id-0uuid4'}) SubElement(assertion, Q_NAMES['saml2:Issuer']).text = 'Test Issuer' self.assertXMLEqual(dump_xml(saml_response.document).decode('utf-8'), dump_xml(root).decode('utf-8'))
def test_post_with_signed_saml_response(self): with cast(TextIO, (DATA_DIR / 'signed_response_and_assertion.xml').open('r')) as f: tree = parse_xml(f.read()) remove_extra_xml_whitespace(tree) saml_response_encoded = b64encode(dump_xml( tree, pretty_print=False)).decode('ascii') response = self.client.post(self.url, {'SAMLResponse': saml_response_encoded}) self.assertIn( '\n <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">', response.context['saml_response']) self.assertIn( '\n <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">', response.context['saml_response']) self.assertEqual(response.context['relay_state'], 'None') self.assertContains(response, '<code>None</code>') self.assertContains(response, '<pre style="white-space: pre-wrap;"><?xml')