def test_decode_wrong_number_of_parts(self): token = self.get_token(issuer='specificCommunicationDefinitionConnectorRequest|extra') with mock.patch.object(LightToken, 'validate'): encoded = token.encode('sha256', self.SECRET) with self.assertRaisesMessage(ParseError, 'wrong number of parts'): LightToken.decode(encoded, 'sha256', self.SECRET)
def get_token(self, issuer: str = None) -> Tuple[LightToken, str]: token = LightToken(id='response-token-id', issuer=issuer or 'response-token-issuer', created=datetime(2017, 12, 11, 14, 12, 5, 148000)) encoded = token.encode('sha256', 'response-token-secret').decode('utf-8') return token, encoded
def get_light_token(self, parameter_name: str, issuer: str, hash_algorithm: str, secret: str, lifetime: Optional[int] = None) -> LightToken: """ Retrieve and verify a light token according to token settings. :param parameter_name: The name of HTTP POST parameter to get the token from. :param issuer: Token issuer. :param hash_algorithm: A hashlib hash algorithm. :param secret: A secret shared between communication parties. :param lifetime: Lifetime of the token (in minutes) until its expiration. :return: A decoded LightToken. :raise ParseError: If the token is malformed and cannot be decoded. :raise ValidationError: If the token can be decoded but model validation fails. :raise SecurityError: If the token digest or issuer is invalid or the token has expired. """ encoded_token = self.request.POST.get(parameter_name, '').encode('utf-8') LOGGER.info('[#%r] Received encoded light token: %r', self.log_id, encoded_token) token = LightToken.decode(encoded_token, hash_algorithm, secret) LOGGER.info('[#%r] Decoded light token: id=%r, issuer=%r', self.log_id, token.id, token.issuer) if token.issuer != issuer: raise SecurityError('Invalid token issuer.') if lifetime and token.created + timedelta( minutes=lifetime) < datetime.now(): raise SecurityError('Token has expired.') return token
def create_light_token(self, issuer: str, hash_algorithm: str, secret: str) -> Tuple[LightToken, str]: """ Create and encode a light token according to token settings. :param issuer: Token issuer. :param hash_algorithm: A hashlib hash algorithm. :param secret: A secret shared between communication parties. :return: A tuple of the token and its encoded form. """ token = LightToken(id=create_xml_uuid(TOKEN_ID_PREFIX), created=datetime.now(), issuer=issuer) LOGGER.info('[#%r] Created light token: id=%r, issuer=%r', self.log_id, token.id, token.issuer) encoded_token = token.encode(hash_algorithm, secret).decode('ascii') LOGGER.info('[#%r] Encoded light token: %r', self.log_id, encoded_token) return token, encoded_token
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)
def test_decode_wrong_digest(self): encoded = (b'c3BlY2lmaWNDb21tdW5pY2F0aW9uRGVmaW5pdGlvbkNvbm5lY3RvclJlcXVlc3R8ODUyYTY0YzAtOGFjMS0' b'0NDVmLWIwZTEtOTkyYWRhNDkzMDMzfDIwMTctMTItMTEgMTQ6MTI6MDUgMTQ4fDdNOHArdVA4Q0tYdU1pMk' b'lxU2RhMXRnNDUyV2xSdmNPU3d1MGRjaXNTWWs9') with self.assertRaisesMessage(SecurityError, 'invalid digest'): LightToken.decode(encoded, 'sha256', self.SECRET)
def test_decode_wrong_secret(self): with self.assertRaisesMessage(SecurityError, 'invalid digest'): LightToken.decode(self.ENCODED_TOKEN, 'sha256', 'Dycky Most!')
def test_decode_max_size_exceeded(self): with self.assertRaisesMessage(ParseError, 'Maximal token size exceeded.'): LightToken.decode(self.ENCODED_TOKEN * 100, 'sha256', self.SECRET)
def test_decode_validation_error(self): with mock.patch.object(LightToken, 'validate'): encoded = self.get_token(issuer='').encode('sha256', self.SECRET) with self.assertRaisesMessage(ValidationError, 'Must be str, not NoneType'): LightToken.decode(encoded, 'sha256', self.SECRET)
def test_decode_ok(self): self.assertEqual(LightToken.decode(self.ENCODED_TOKEN, 'sha256', self.SECRET), self.get_token())
def get_token(self, **kwargs) -> LightToken: data = self.VALID_DATA.copy() data.update(**kwargs) return LightToken(**data)
def test_post_success(self, uuid_mock: MagicMock): self.maxDiff = None saml_request_xml, saml_request_encoded = self.load_saml_request( signed=True) light_request = LightRequest(**LIGHT_REQUEST_DICT) light_request.issuer = 'https://example.net/EidasNode/ConnectorMetadata' self.cache_mock.get_and_remove.return_value = dump_xml( light_request.export_xml()).decode('utf-8') response = self.client.post( self.url, { 'SAMLRequest': saml_request_encoded, 'RelayState': 'relay123', 'country_param': 'ca' }) # Context self.assertIn('token', response.context) self.assertEqual(response.context['token_parameter'], 'test_request_token') self.assertEqual(response.context['eidas_url'], 'http://test.example.net/SpecificConnectorRequest') self.assertEqual(response.context['error'], None) # Token encoded_token = response.context['token'] token = LightToken.decode(encoded_token, 'sha256', 'request-token-secret') self.assertEqual(token.id, 'T0uuid4') self.assertEqual(token.issuer, 'request-token-issuer') self.assertEqual(token.created, datetime(2017, 12, 11, 14, 12, 5)) # Storing light request light_request_data = LIGHT_REQUEST_DICT.copy() light_request_data.update({ 'id': 'test-saml-request-id', 'issuer': 'test-connector-request-issuer', }) light_request = LightRequest(**light_request_data) light_request.requested_attributes = light_request.requested_attributes.copy( ) del light_request.requested_attributes[ 'http://eidas.europa.eu/attributes/naturalperson/AdditionalAttribute'] del light_request.requested_attributes[ 'http://eidas.europa.eu/attributes/legalperson/LegalAdditionalAttribute'] 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-connector-request-cache'), call.get_cache().put( 'T0uuid4', dump_xml(light_request.export_xml()).decode('utf-8')) ]) # Rendering self.assertContains(response, 'Redirect to eIDAS Node is in progress') self.assertContains(response, 'eidas_node/connector/formautosubmit.js') self.assertContains( response, '<form class="auto-submit" ' 'action="http://test.example.net/SpecificConnectorRequest"') self.assertContains( response, '<input type="hidden" name="test_request_token" value="{}"'.format( encoded_token)) self.assertNotIn(b'An error occurred', response.content)