Ejemplo n.º 1
0
 def test_decrypt_xml_with_document_encrypted(self):
     self.maxDiff = None
     with cast(BinaryIO,
               (DATA_DIR / 'saml_response_decrypted.xml').open('rb')) as f:
         document_decrypted = parse_xml(f.read())
     with cast(BinaryIO,
               (DATA_DIR / 'saml_response_encrypted.xml').open('rb')) as f:
         document_encrypted = parse_xml(f.read())
     expected = dump_xml(document_decrypted).decode('utf-8')
     self.assertEqual(decrypt_xml(document_encrypted, KEY_FILE), 1)
     actual = dump_xml(document_encrypted).decode('utf-8')
     self.assertXMLEqual(expected, actual)
Ejemplo n.º 2
0
 def test_load_xml_attributes_definition_element(self):
     data = parse_xml(b'<lightRequest><requestedAttributes><attribute>data</attribute>'
                      b'</requestedAttributes></lightRequest>')
     with self.assertRaisesMessage(ValidationError,
                                   "'<lightRequest><requestedAttributes><attribute>': "
                                   "'Missing attribute.definition element.'"):
         LightRequest.load_xml(data)
     data = parse_xml(b'<lightRequest><requestedAttributes><attribute><foo>data</foo>'
                      b'</attribute></requestedAttributes></lightRequest>')
     with self.assertRaisesMessage(ValidationError,
                                   '\'<lightRequest><requestedAttributes><attribute><foo>\': '
                                   '"Unexpected element \'foo\'"'):
         LightRequest.load_xml(data)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    def test_verify_xml_signatures_fail(self):
        with cast(TextIO, (DATA_DIR / 'signed_response.xml').open('r')) as f:
            tree = parse_xml(f.read())

        # Fails because of pretty printing
        self.assertRaises(SecurityError, verify_xml_signatures, tree,
                          CERT_FILE)
Ejemplo n.º 5
0
    def get_saml_response(self, key_file: Optional[str],
                          cert_file: Optional[str]) -> SAMLResponse:
        """
        Extract and decrypt a SAML response from POST data.

        :param key_file: An optional path to a key to decrypt the response.
        :param cert_file: An optional path to a certificate to verify the response.
        :return: A SAML response.
        """
        raw_response = b64decode(
            self.request.POST.get('SAMLResponse',
                                  '').encode('ascii')).decode('utf-8')
        LOGGER.debug('Raw SAML Response: %s', raw_response)

        try:
            response = SAMLResponse(parse_xml(raw_response),
                                    self.request.POST.get('RelayState'))
        except XMLSyntaxError as e:
            raise ParseError(str(e)) from None

        LOGGER.info(
            '[#%r] Received SAML response: id=%r, issuer=%r, in_response_to_id=%r',
            self.log_id, response.id, response.issuer,
            response.in_response_to_id)

        if cert_file:
            response.verify_response(cert_file)
        if key_file:
            response.decrypt(key_file)
        if cert_file:
            response.verify_assertion(cert_file)
        return response
Ejemplo n.º 6
0
 def test_decrypt_xml_with_document_encrypted_wrong_key(self):
     self.maxDiff = None
     with cast(BinaryIO,
               (DATA_DIR / 'saml_response_encrypted.xml').open('rb')) as f:
         document_encrypted = parse_xml(f.read())
     self.assertRaises(xmlsec.Error, decrypt_xml, document_encrypted,
                       WRONG_KEY_FILE)
 def test_load_xml_wrong_field_element(self):
     with self.assertRaisesMessage(
             ValidationError,
             '\'<xmlUser><name><familyName>\': "Unknown element \'familyName\'."'
     ):
         XMLUser.load_xml(
             parse_xml(XML_USER_DATA.replace('lastName', 'familyName')))
Ejemplo n.º 8
0
 def pop_light_response(self, uid: str) -> Optional[LightResponse]:
     """Look up a LightResponse by a unique id and then remove it."""
     data = self.get_cache(self.response_cache_name).get_and_remove(uid)
     LOGGER.debug('Got Light Response from cache: id=%r, data=%s', uid,
                  data)
     return LightResponse().load_xml(
         parse_xml(data)) if data is not None else None
Ejemplo n.º 9
0
 def test_load_xml_attributes_unexpected_element(self):
     data = parse_xml(b'<lightRequest><requestedAttributes><myField>data</myField>'
                      b'</requestedAttributes></lightRequest>')
     with self.assertRaisesMessage(ValidationError,
                                   '\'<lightRequest><requestedAttributes><myField>\': '
                                   '"Unexpected element \'myField\'"'):
         LightRequest.load_xml(data)
Ejemplo n.º 10
0
    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))
Ejemplo n.º 11
0
    def get_saml_request(self, country_parameter: str,
                         cert_file: Optional[str]) -> SAMLRequest:
        """
        Extract and decrypt a SAML request from POST data.

        :param country_parameter: A parameter containing citizen country code.
        :param cert_file: The path of a certificate to verify the signature.
        :return: A SAML request.
        """
        try:
            request = SAMLRequest(
                parse_xml(
                    b64decode(
                        self.request.POST.get('SAMLRequest',
                                              '').encode('ascii'))),
                self.request.POST[country_parameter].upper(),
                self.request.POST.get('RelayState'))
        except XMLSyntaxError as e:
            raise ParseError(str(e)) from None

        LOGGER.info('[#%r] Received SAML request: id=%r, issuer=%r',
                    self.log_id, request.id, request.issuer)
        if cert_file:
            request.verify_request(cert_file)
        return request
Ejemplo n.º 12
0
    def test_load_xml_with_response_status_failure(self):
        self.maxDiff = None
        response = self.create_response(False)
        with cast(BinaryIO, (DATA_DIR / 'light_response_failure.xml').open('r')) as f:
            data = f.read()

        self.assertEqual(LightResponse.load_xml(parse_xml(data)), response)
Ejemplo n.º 13
0
    def test_export_xml_full_sample(self):
        self.maxDiff = None

        with cast(BinaryIO, (DATA_DIR / 'light_request.xml').open('rb')) as f:
            data = f.read()
            request = LightRequest.load_xml(parse_xml(data))
        self.assertEqual(dump_xml(request.export_xml()), data)
Ejemplo n.º 14
0
 def test_load_xml_attribute_values_unexpected_element(self):
     data = parse_xml(b'<lightRequest><requestedAttributes><attribute><definition>data</definition><foo/>'
                      b'</attribute></requestedAttributes></lightRequest>')
     with self.assertRaisesMessage(ValidationError,
                                   '\'<lightRequest><requestedAttributes><attribute><foo>\': '
                                   '"Unexpected element \'foo\'"'):
         LightRequest.load_xml(data)
Ejemplo n.º 15
0
    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)
Ejemplo n.º 16
0
    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)
Ejemplo n.º 17
0
 def test_create_light_response_failed_response(self):
     self.maxDiff = None
     with cast(BinaryIO,
               (DATA_DIR / 'saml_response_failed.xml').open('rb')) as f:
         response = SAMLResponse(parse_xml(f), 'relay123')
     self.assertEqual(response.create_light_response(),
                      self.create_light_response(False))
Ejemplo n.º 18
0
    def test_create_light_response_not_encrypted(self):
        self.maxDiff = None
        with cast(BinaryIO, (DATA_DIR / 'saml_response.xml').open('rb')) as f:
            saml_response = SAMLResponse(parse_xml(f), 'relay123')

        light_response = saml_response.create_light_response()
        self.assertEqual(light_response, self.create_light_response(True))
Ejemplo n.º 19
0
    def post(self, request: HttpRequest) -> HttpResponse:
        """Handle a HTTP POST request."""
        saml_response_xml = b64decode(self.request.POST.get('SAMLResponse', '').encode('ascii')).decode('utf-8')
        if saml_response_xml:
            # Verify signatures
            cert_file = (CONNECTOR_SETTINGS.service_provider['response_signature'] or {}).get('cert_file')
            if cert_file:
                response = SAMLResponse(parse_xml(saml_response_xml))
                response.verify_response(cert_file)
                response.verify_assertion(cert_file)

            # Reformat with pretty printing for display
            saml_response_xml = dump_xml(parse_xml(saml_response_xml)).decode('utf-8')

        self.saml_response = saml_response_xml
        self.relay_state = self.request.POST.get('RelayState')
        return self.get(request)
Ejemplo n.º 20
0
    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)
Ejemplo n.º 21
0
 def test_create_light_request_our_issuer_set(self):
     saml_request_xml, _saml_request_encoded = self.load_saml_request()
     view = ServiceProviderRequestView()
     view.saml_request = SAMLRequest(parse_xml(saml_request_xml), 'ca',
                                     'xyz')
     light_request = view.create_light_request('test-saml-request-issuer',
                                               'test-light-request-issuer')
     self.assertEqual(light_request.issuer, 'test-light-request-issuer')
Ejemplo n.º 22
0
    def test_create_light_request_success(self):
        self.maxDiff = None
        with cast(TextIO, (DATA_DIR / 'saml_request.xml').open('r')) as f:
            data = f.read()

        saml_request = SAMLRequest(parse_xml(data), 'CA', 'relay123')
        self.assertEqual(
            saml_request.create_light_request().get_data_as_dict(),
            LIGHT_REQUEST_DICT)
Ejemplo n.º 23
0
 def test_create_light_request_wrong_issuer(self):
     saml_request_xml, _saml_request_encoded = self.load_saml_request()
     view = ServiceProviderRequestView()
     view.saml_request = SAMLRequest(parse_xml(saml_request_xml), 'ca',
                                     'xyz')
     with self.assertRaisesMessage(SecurityError,
                                   'Invalid SAML request issuer'):
         view.create_light_request('wrong-saml-issuer',
                                   'test-light-request-issuer')
Ejemplo n.º 24
0
    def test_create_light_response_with_unsupported_sub_status(self):
        with cast(BinaryIO,
                  (DATA_DIR / 'saml_response_failed_unsupported_sub_status.xml'
                   ).open('rb')) as f:
            response = SAMLResponse(parse_xml(f), 'relay123')

        expected = self.create_light_response(False)
        expected.status.sub_status_code = None
        self.assertEqual(response.create_light_response(), expected)
Ejemplo n.º 25
0
    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)
Ejemplo n.º 26
0
    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')
Ejemplo n.º 27
0
    def test_create_light_response_with_status_version_mismatch(self):
        with cast(
                BinaryIO,
            (DATA_DIR /
             'saml_response_failed_version_mismatch.xml').open('rb')) as f:
            response = SAMLResponse(parse_xml(f), 'relay123')

        expected = self.create_light_response(False)
        expected.status.status_code = StatusCode.REQUESTER
        expected.status.sub_status_code = SubStatusCode.VERSION_MISMATCH
        self.assertEqual(response.create_light_response(), expected)
Ejemplo n.º 28
0
    def test_create_light_response_correct_id_and_issuer(self):
        self.maxDiff = None
        view = IdentityProviderResponseView()
        view.request = self.factory.post(self.url)

        with cast(TextIO, (DATA_DIR / 'saml_response.xml').open('r')) as f:
            view.saml_response = SAMLResponse(parse_xml(f.read()), 'relay123')

        light_response = view.create_light_response('test-light-response-issuer')
        self.assertEqual(light_response.id, 'test-saml-response-id')  # Preserved
        self.assertEqual(light_response.in_response_to_id, 'test-saml-request-id')  # Preserved
        self.assertEqual(light_response.issuer, 'test-light-response-issuer')  # Replaced
Ejemplo n.º 29
0
    def test_put_light_response(self):
        with cast(TextIO, (DATA_DIR / 'light_response.xml').open('r')) as f:
            data = f.read()

        response = LightResponse.load_xml(parse_xml(data))
        self.storage.put_light_response('abc', response)
        self.assertEqual(self.client_class_mock.mock_calls, [call(timeout=33)])
        self.assertEqual(self.client_mock.mock_calls, [
            call.connect(self.HOST, self.PORT),
            call.get_cache(self.RESPONSE_CACHE_NAME),
            call.get_cache().put('abc', data)
        ])
Ejemplo n.º 30
0
    def test_from_light_request_invalid_id(self):
        self.maxDiff = None

        with cast(BinaryIO,
                  (DATA_DIR / 'light_request_minimal.xml').open('rb')) as f:
            request = LightRequest.load_xml(parse_xml(f))
        request.id = '0day'

        with self.assert_validation_error(
                'id', "Light request id is not a valid XML id: '0day'"):
            SAMLRequest.from_light_request(
                request, 'test/destination',
                datetime(2017, 12, 11, 14, 12, 5, 148000))