from unittest.mock import AsyncMock, MagicMock import pytest from asyncua import ua from asyncua.crypto.security_policies import ( SecurityPolicyAes128Sha256RsaOaep, SecurityPolicyBasic256Sha256, ) from ingestor.connection.security import ( SESSION_TIMEOUT_MS, UnsupportedSecurityError, apply_security, is_secured, ) CERTIFICATE = {'certificate_id': 1, 'application_uri': 'urn:sientia:opc-ingestor'} def _client(): client = MagicMock() client.set_security = AsyncMock() return client @pytest.mark.asyncio async def test_apply_security_uses_mode_and_policy_and_channel_lifetime(): client = _client() await apply_security( client, {'mode': 'SignAndEncrypt', 'policy': 'Basic256Sha256'}, CERTIFICATE, 'c', 'k' ) assert client.application_uri == 'urn:sientia:opc-ingestor' assert client.session_timeout == SESSION_TIMEOUT_MS assert client.secure_channel_timeout == int(SESSION_TIMEOUT_MS * 1.25) args, kwargs = client.set_security.call_args assert args[0] is SecurityPolicyBasic256Sha256 assert kwargs == { 'certificate': 'c', 'private_key': 'k', 'server_certificate': None, 'mode': ua.MessageSecurityMode.SignAndEncrypt, } @pytest.mark.asyncio async def test_apply_security_sign_with_another_policy(): client = _client() await apply_security( client, {'mode': 'Sign', 'policy': 'Aes128Sha256RsaOaep'}, CERTIFICATE, 'c', 'k' ) args, kwargs = client.set_security.call_args assert args[0] is SecurityPolicyAes128Sha256RsaOaep assert kwargs['mode'] == ua.MessageSecurityMode.Sign @pytest.mark.asyncio @pytest.mark.parametrize( 'security', [ {'mode': 'Encrypt', 'policy': 'Basic256Sha256'}, {'mode': 'Sign', 'policy': 'Basic999'}, {'mode': 'Sign', 'policy': 'None'}, {}, ], ) async def test_apply_security_rejects_unknown_mode_or_policy(security): client = _client() with pytest.raises(UnsupportedSecurityError): await apply_security(client, security, CERTIFICATE, 'c', 'k') client.set_security.assert_not_awaited() @pytest.mark.parametrize( ('server_cfg', 'secured'), [ ({'security': {'mode': 'SignAndEncrypt'}, 'certificate': CERTIFICATE}, True), ({'security': {'mode': 'Sign'}, 'certificate': CERTIFICATE}, True), # No certificate: no security (D5), whatever the mode ({'security': {'mode': 'SignAndEncrypt'}, 'certificate': None}, False), ({'security': {'mode': 'SignAndEncrypt'}}, False), # mode None: no security even with a certificate ({'security': {'mode': 'None'}, 'certificate': CERTIFICATE}, False), ({}, False), ], ) def test_is_secured(server_cfg, secured): assert is_secured(server_cfg) is secured