예제 #1
0
    def create_light_response(self, auth_class_map: Dict[str, LevelOfAssurance] = None) -> LightResponse:
        """Convert SAML response to light response."""
        response = LightResponse(attributes=OrderedDict())
        root = self.document.getroot()
        if root.tag != Q_NAMES['saml2p:Response']:
            raise ValidationError({
                get_element_path(root): 'Wrong root element: {!r}'.format(root.tag)})

        response.id = root.get('ID')
        response.in_response_to_id = root.get('InResponseTo')
        response.issuer = self.issuer

        response.status = status = Status()
        status_code_elm = root.find('./{}/{}'.format(Q_NAMES['saml2p:Status'], Q_NAMES['saml2p:StatusCode']))
        if status_code_elm is not None:
            sub_status_code_elm = status_code_elm.find('./{}'.format(Q_NAMES['saml2p:StatusCode']))
            status_message_elm = root.find('./{}/{}'.format(Q_NAMES['saml2p:Status'], Q_NAMES['saml2p:StatusMessage']))

            status_code = status_code_elm.get('Value')
            sub_status_code = sub_status_code_elm.get('Value') if sub_status_code_elm is not None else None

            if status_code == SubStatusCode.VERSION_MISMATCH.value:
                # VERSION_MISMATCH is a status code in SAML 2 but a sub status code in Light response!
                status.status_code = StatusCode.REQUESTER
                status.sub_status_code = SubStatusCode.VERSION_MISMATCH
            else:
                status.status_code = StatusCode(status_code)
                try:
                    status.sub_status_code = SubStatusCode(sub_status_code)
                except ValueError:
                    # None or a sub status codes not recognized by eIDAS
                    status.sub_status_code = None

            status.failure = status.status_code != StatusCode.SUCCESS
            if status_message_elm is not None:
                status.status_message = status_message_elm.text

        assertion_elm = self.assertion
        if assertion_elm is not None:
            try:
                self._parse_assertion(response, assertion_elm, auth_class_map)
            except ValidationError as e:
                return LightResponse(
                    id=root.get('ID'),
                    in_response_to_id=root.get('InResponseTo'),
                    issuer=self.issuer,
                    status=Status(
                        failure=True,
                        status_code=StatusCode.RESPONDER,
                        status_message='Invalid data of {!r}: {!r}'.format(*e.errors.popitem())))

        response.relay_state = self.relay_state
        return response
예제 #2
0
 def set_failure(self, failure: bool) -> None:
     data = self.__class__.VALID_DATA.copy()
     if failure:
         self.OPTIONAL = self.__class__.OPTIONAL_FAILURE
         data.update({
             'status': Status(failure=failure,
                              status_code=StatusCode.REQUESTER,
                              sub_status_code=SubStatusCode.REQUEST_DENIED,
                              status_message='Oops.'),
             'attributes': OrderedDict(),
             'subject': None,
             'subject_name_id_format': None,
             'level_of_assurance': None,
         })
     else:
         self.OPTIONAL = self.__class__.OPTIONAL
         data['status'] = Status(failure=False)
     self.VALID_DATA = data
    def test_rewrite_name_id_persistent(self):
        light_response_data = LIGHT_RESPONSE_DICT.copy()
        light_response_data['status'] = Status(**light_response_data['status'])
        light_response_data['subject_name_id_format'] = NameIdFormat.PERSISTENT
        view = IdentityProviderResponseView()
        view.light_response = LightResponse(**light_response_data)
        view.auxiliary_data = {'name_id_format': NameIdFormat.PERSISTENT.value}

        view.rewrite_name_id()
        self.assertEqual(view.light_response.subject_name_id_format, NameIdFormat.PERSISTENT)
        self.assertEqual(view.light_response.subject, 'CZ/CZ/ff70c9dd-6a05-4068-aaa2-b57be4f328e9')
    def test_rewrite_name_id_unspecified_to_transient(self, uuid_mock):
        light_response_data = LIGHT_RESPONSE_DICT.copy()
        light_response_data['status'] = Status(**light_response_data['status'])
        light_response_data['subject_name_id_format'] = NameIdFormat.UNSPECIFIED
        view = IdentityProviderResponseView()
        view.light_response = LightResponse(**light_response_data)
        view.auxiliary_data = {'name_id_format': NameIdFormat.TRANSIENT.value}

        view.rewrite_name_id()
        self.assertEqual(view.light_response.subject_name_id_format, NameIdFormat.TRANSIENT)
        self.assertEqual(view.light_response.subject, '0uuid4')
    def test_rewrite_name_id_failure(self):
        light_response_data = FAILED_LIGHT_RESPONSE_DICT.copy()
        light_response_data['status'] = Status(**light_response_data['status'])
        light_response_data['subject_name_id_format'] = NameIdFormat.PERSISTENT
        view = IdentityProviderResponseView()
        view.light_response = LightResponse(**light_response_data)
        view.auxiliary_data = {'name_id_format': NameIdFormat.TRANSIENT.value}

        view.rewrite_name_id()
        self.assertEqual(view.light_response.subject_name_id_format, NameIdFormat.PERSISTENT)
        self.assertIsNone(view.light_response.subject)
    def test_create_light_token(self, uuid_mock: MagicMock):
        view = IdentityProviderResponseView()
        view.request = self.factory.post(self.url)
        light_response_data = LIGHT_RESPONSE_DICT.copy()
        light_response_data['status'] = Status(**light_response_data['status'])
        view.light_response = LightResponse(**light_response_data)

        token, encoded_token = view.create_light_token('test-token-issuer', 'sha256', 'test-secret')
        self.assertEqual(token.id, 'T0uuid4')
        self.assertEqual(token.issuer, 'test-token-issuer')
        self.assertEqual(token.created, datetime(2017, 12, 11, 16, 12, 5))
        self.assertEqual(token.encode('sha256', 'test-secret').decode('ascii'), encoded_token)
        self.assertEqual(uuid_mock.mock_calls, [call()])
    def test_from_light_response_minimal(self):
        self.maxDiff = None
        status = Status(failure=False)
        response = self.create_light_response(True,
                                              ip_address=None,
                                              status=status,
                                              attributes={})
        saml_response = SAMLResponse.from_light_response(
            response, None, None, datetime(2017, 12, 11, 14, 12, 5, 148000),
            timedelta(minutes=5))

        with cast(
                TextIO,
            (DATA_DIR /
             'saml_response_from_light_response_minimal.xml').open('r')) as f2:
            data = f2.read()
        self.assertXMLEqual(
            dump_xml(saml_response.document).decode('utf-8'), data)
    def test_from_light_response_version_mismatch(self):
        self.maxDiff = None
        status = Status(failure=True,
                        sub_status_code=SubStatusCode.VERSION_MISMATCH,
                        status_message='Oops.')
        response = self.create_light_response(False,
                                              issuer=None,
                                              ip_address=None,
                                              status=status)
        saml_response = SAMLResponse.from_light_response(
            response, None, None, datetime(2017, 12, 11, 14, 12, 5, 148000),
            timedelta(minutes=5))

        with cast(TextIO,
                  (DATA_DIR /
                   'saml_response_from_light_response_version_mismatch.xml'
                   ).open('r')) as f2:
            data = f2.read()
        self.assertXMLEqual(
            dump_xml(saml_response.document).decode('utf-8'), data)
    def test_post_success(self, uuid_mock: MagicMock):
        with cast(BinaryIO, (DATA_DIR / 'saml_response.xml').open('rb')) as f:
            saml_request_xml = f.read()

        response = self.client.post(self.url, {'SAMLResponse': b64encode(saml_request_xml).decode('ascii'),
                                               'RelayState': 'relay123'})

        # Context
        self.assertIn('token', response.context)
        self.assertEqual(response.context['token_parameter'], 'test_token')
        self.assertEqual(response.context['eidas_url'], 'https://test.example.net/SpecificProxyServiceResponse')
        self.assertEqual(response.context['error'], None)

        # Token
        encoded_token = response.context['token']
        token = LightToken.decode(encoded_token, 'sha256', 'response-token-secret')
        self.assertEqual(token.id, 'T0uuid4')
        self.assertEqual(token.issuer, 'response-token-issuer')
        self.assertEqual(token.created, datetime(2017, 12, 11, 14, 12, 5))

        # Storing light response
        light_response_data = LIGHT_RESPONSE_DICT.copy()
        light_response_data.update({
            'status': Status(**light_response_data['status']),
            'id': 'test-saml-response-id',  # Preserved
            'in_response_to_id': 'test-saml-request-id',  # Preserved
            'issuer': 'https://test.example.net/node-proxy-service-response',  # Replaced
        })
        light_response = LightResponse(**light_response_data)
        self.assertEqual(self.client_class_mock.mock_calls, [call(timeout=66)])
        self.assertEqual(self.client_mock.mock_calls,
                         [call.connect('test.example.net', 1234),
                          call.get_cache('test-proxy-service-response-cache'),
                          call.get_cache().put('T0uuid4', dump_xml(light_response.export_xml()).decode('utf-8'))])

        # Rendering
        self.assertContains(response, 'Redirect to eIDAS Node is in progress')
        self.assertContains(response,
                            '<form class="auto-submit" action="https://test.example.net/SpecificProxyServiceResponse"')
        self.assertContains(response, '<input type="hidden" name="test_token" value="{}"'.format(encoded_token))
        self.assertNotIn(b'An error occurred', response.content)
예제 #10
0
 def create_response(self, success: bool) -> LightResponse:
     data = (LIGHT_RESPONSE_DICT if success else FAILED_LIGHT_RESPONSE_DICT).copy()
     data['status'] = Status(**data['status'])
     return LightResponse(**data)
예제 #11
0
class TestLightResponse(ValidationMixin, SimpleTestCase):
    MODEL = LightResponse
    OPTIONAL = {'issuer', 'ip_address', 'relay_state'}
    OPTIONAL_FAILURE = {'issuer', 'ip_address', 'relay_state',
                        'subject', 'subject_name_id_format', 'level_of_assurance'}
    VALID_DATA = {
        'id': 'uuid',
        'in_response_to_id': 'uuid2',
        'issuer': 'MyIssuer',
        'ip_address': '127.0.0.1',
        'relay_state': 'state 123',
        'subject': 'my subject',
        'subject_name_id_format': NameIdFormat.PERSISTENT,
        'level_of_assurance': LevelOfAssurance.LOW,
        'status': Status(failure=False),
        'attributes': OrderedDict(
            [('http://eidas.europa.eu/attributes/naturalperson/CurrentFamilyName', []),
             ('http://eidas.europa.eu/attributes/naturalperson/CurrentGivenName', ['Antonio', 'Lucio'])]),
    }
    INVALID_DATA = {
        'id': 1,
        'in_response_to_id': 2,
        'issuer': 3,
        'ip_address': 4,
        'relay_state': 5,
        'subject': 6,
        'subject_name_id_format': str(NameIdFormat.PERSISTENT),
        'level_of_assurance': str(LevelOfAssurance.LOW),
        'status': Status(),
        'attributes': ['attr1'],
    }

    def tearDown(self) -> None:
        if self.VALID_DATA is not self.__class__.VALID_DATA:
            del self.VALID_DATA
        if self.OPTIONAL is not self.__class__.OPTIONAL:
            del self.OPTIONAL

    def create_response(self, success: bool) -> LightResponse:
        data = (LIGHT_RESPONSE_DICT if success else FAILED_LIGHT_RESPONSE_DICT).copy()
        data['status'] = Status(**data['status'])
        return LightResponse(**data)

    def set_failure(self, failure: bool) -> None:
        data = self.__class__.VALID_DATA.copy()
        if failure:
            self.OPTIONAL = self.__class__.OPTIONAL_FAILURE
            data.update({
                'status': Status(failure=failure,
                                 status_code=StatusCode.REQUESTER,
                                 sub_status_code=SubStatusCode.REQUEST_DENIED,
                                 status_message='Oops.'),
                'attributes': OrderedDict(),
                'subject': None,
                'subject_name_id_format': None,
                'level_of_assurance': None,
            })
        else:
            self.OPTIONAL = self.__class__.OPTIONAL
            data['status'] = Status(failure=False)
        self.VALID_DATA = data

    def test_required_for_failure(self):
        self.set_failure(True)
        self.test_required()

    def test_attributes_with_response_status_ok(self):
        self.set_failure(False)
        self.assert_attributes_valid('attributes')

    def test_attributes_with_response_status_failure(self):
        self.set_failure(True)
        self.assert_attributes_valid('attributes')

    def test_export_xml_with_response_status_ok(self):
        self.maxDiff = None
        response = self.create_response(True)
        with cast(BinaryIO, (DATA_DIR / 'light_response.xml').open('r')) as f:
            data = f.read()
        self.assertEqual(dump_xml(response.export_xml()).decode('utf-8'), data)

    def test_export_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(dump_xml(response.export_xml()).decode('utf-8'), data)

    def test_load_xml_with_response_status_ok(self):
        self.maxDiff = None
        self.set_failure(False)
        response = self.create_response(True)
        with cast(BinaryIO, (DATA_DIR / 'light_response.xml').open('r')) as f:
            data = f.read()
        self.assertEqual(LightResponse.load_xml(parse_xml(data)), response)

    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)
예제 #12
0
 def test_deserialize_sub_status_code_invalid(self):
     status = Status()
     elm = Element('subStatusCode')
     for invalid in '##', 'test ## test':
         elm.text = invalid
         self.assertIsNone(status.deserialize_sub_status_code(elm))
 def get_light_response(self, **kwargs) -> LightResponse:
     light_response_data = LIGHT_RESPONSE_DICT.copy()
     light_response_data['status'] = Status(**light_response_data['status'])
     light_response_data.update(**kwargs)
     return LightResponse(**light_response_data)
 def get_light_response_data(self, subject_id: str) -> dict:
     data = deepcopy(LIGHT_RESPONSE_DICT)
     data['status'] = Status(**data['status'])
     data['subject'] = subject_id
     data['attributes'][PERSON_ID][0] = subject_id
     return data