test_failures.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. from openleadr import OpenADRClient, OpenADRServer
  2. from openleadr.utils import generate_id
  3. from openleadr import messaging, errors
  4. import pytest
  5. from aiohttp import web
  6. import os
  7. import logging
  8. import asyncio
  9. from datetime import timedelta
  10. from base64 import b64encode
  11. import re
  12. from lxml import etree
  13. @pytest.mark.asyncio
  14. async def test_http_level_error(start_server):
  15. client = OpenADRClient(vtn_url="http://this.is.an.error", ven_name=VEN_NAME)
  16. client.on_event = _client_on_event
  17. await client.run()
  18. await client.client_session.close()
  19. @pytest.mark.asyncio
  20. async def test_xml_schema_error(start_server, caplog):
  21. message = messaging.create_message("oadrQueryRegistration", request_id='req1234')
  22. message = message.replace('<requestID xmlns="http://docs.oasis-open.org/ns/energyinterop/201110/payloads">req1234</requestID>', '')
  23. client = OpenADRClient(ven_name='myven', vtn_url=f'http://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b')
  24. result = await client._perform_request('EiRegisterParty', message)
  25. assert result == (None, {})
  26. logs = [rec.message for rec in caplog.records]
  27. for log in logs:
  28. if log.startswith("Non-OK status 400"):
  29. assert "XML failed validation" in log
  30. break
  31. else:
  32. assert False
  33. @pytest.mark.asyncio
  34. async def test_wrong_endpoint(start_server, caplog):
  35. message = messaging.create_message("oadrQueryRegistration", request_id='req1234')
  36. client = OpenADRClient(ven_name='myven', vtn_url=f'http://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b')
  37. response_type, response_payload = await client._perform_request('OadrPoll', message)
  38. assert response_type == 'oadrResponse'
  39. assert response_payload['response']['response_code'] == 459
  40. @pytest.mark.asyncio
  41. async def test_vtn_no_create_party_registration_handler(caplog):
  42. caplog.set_level(logging.WARNING)
  43. server = OpenADRServer(vtn_id='myvtn')
  44. client = OpenADRClient(ven_name='myven', vtn_url='http://localhost:8080/OpenADR2/Simple/2.0b')
  45. await server.run_async()
  46. await client.run()
  47. await asyncio.sleep(0.5)
  48. await server.stop()
  49. await client.stop()
  50. assert len(caplog.messages) == 2
  51. assert 'No VEN ID received from the VTN, aborting.' in caplog.messages
  52. assert ("You should implement and register your own on_create_party_registration "
  53. "handler if you want VENs to be able to connect to you. This handler will "
  54. "receive a registration request and should return either 'False' (if the "
  55. "registration is denied) or a (ven_id, registration_id) tuple if the "
  56. "registration is accepted.") in caplog.messages
  57. @pytest.mark.asyncio
  58. async def test_invalid_signature_error(start_server_with_signatures, caplog):
  59. client = OpenADRClient(ven_name='myven',
  60. vtn_url=f'https://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b',
  61. cert=VEN_CERT,
  62. key=VEN_KEY,
  63. vtn_fingerprint='EE:44:C5:78:7E:4B:B8:DC:84:1F')
  64. message = client._create_message('oadrPoll', ven_id='ven123')
  65. fake_sig = b64encode("HelloThere".encode('utf-8')).decode('utf-8')
  66. message = re.sub(r'<ds:SignatureValue>.*?</ds:SignatureValue>', f'<ds:SignatureValue>{fake_sig}</ds:SignatureValue>', message)
  67. result = await client._perform_request('OadrPoll', message)
  68. assert result == (None, {})
  69. logs = [rec.message for rec in caplog.records]
  70. for log in logs:
  71. if log.startswith("Non-OK status 403 when performing a request"):
  72. assert "Invalid Signature" in log
  73. break
  74. else:
  75. assert False
  76. def problematic_handler(*args, **kwargs):
  77. raise Exception("BOOM")
  78. @pytest.mark.asyncio
  79. async def test_server_handler_exception(caplog):
  80. server = OpenADRServer(vtn_id=VTN_ID,
  81. http_port=SERVER_PORT)
  82. server.add_handler('on_create_party_registration', problematic_handler)
  83. await server.run_async()
  84. client = OpenADRClient(ven_name='myven',
  85. vtn_url=f'http://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b')
  86. await client.run()
  87. await asyncio.sleep(0.5)
  88. await client.stop()
  89. await server.stop()
  90. for message in caplog.messages:
  91. if message.startswith('Non-OK status 500 when performing a request'):
  92. break
  93. else:
  94. assert False
  95. def protocol_error_handler(*args, **kwargs):
  96. raise errors.OutOfSequenceError()
  97. @pytest.mark.asyncio
  98. async def test_throw_protocol_error(caplog):
  99. server = OpenADRServer(vtn_id=VTN_ID,
  100. http_port=SERVER_PORT)
  101. server.add_handler('on_create_party_registration', protocol_error_handler)
  102. await server.run_async()
  103. client = OpenADRClient(ven_name='myven',
  104. vtn_url=f'http://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b')
  105. await client.run()
  106. await asyncio.sleep(0.5)
  107. await client.stop()
  108. await server.stop()
  109. assert 'We got a non-OK OpenADR response from the server: 450: OUT OF SEQUENCE' in caplog.messages
  110. @pytest.mark.asyncio
  111. async def test_invalid_signature_error(start_server_with_signatures, caplog):
  112. client = OpenADRClient(ven_name='myven',
  113. vtn_url=f'https://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b',
  114. cert=VEN_CERT,
  115. key=VEN_KEY,
  116. vtn_fingerprint='EE:44:C5:78:7E:4B:B8:DC:84:1F')
  117. message = client._create_message('oadrPoll', ven_id='ven123')
  118. fake_sig = b64encode("HelloThere".encode('utf-8')).decode('utf-8')
  119. message = re.sub(r'<ds:SignatureValue>.*?</ds:SignatureValue>', f'<ds:SignatureValue>{fake_sig}</ds:SignatureValue>', message)
  120. result = await client._perform_request('OadrPoll', message)
  121. assert result == (None, {})
  122. logs = [rec.message for rec in caplog.records]
  123. for log in logs:
  124. if log.startswith("Non-OK status 403 when performing a request"):
  125. assert "Invalid Signature" in log
  126. break
  127. else:
  128. assert False
  129. def test_replay_protect_message_too_old(caplog):
  130. client = OpenADRClient(ven_name='myven',
  131. vtn_url=f'https://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b',
  132. cert=VEN_CERT,
  133. key=VEN_KEY,
  134. vtn_fingerprint='EE:44:C5:78:7E:4B:B8:DC:84:1F')
  135. _temp = messaging.REPLAY_PROTECT_MAX_TIME_DELTA
  136. messaging.REPLAY_PROTECT_MAX_TIME_DELTA = timedelta(seconds=0)
  137. message = client._create_message('oadrPoll', ven_id='ven123')
  138. tree = etree.fromstring(message.encode('utf-8'))
  139. with pytest.raises(ValueError) as err:
  140. messaging._verify_replay_protect(tree)
  141. assert str(err.value) == 'The message was signed too long ago.'
  142. messaging.REPLAY_PROTECT_MAX_TIME_DELTA = _temp
  143. def test_replay_protect_repeated_message(caplog):
  144. client = OpenADRClient(ven_name='myven',
  145. vtn_url=f'https://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b',
  146. cert=VEN_CERT,
  147. key=VEN_KEY,
  148. vtn_fingerprint='EE:44:C5:78:7E:4B:B8:DC:84:1F')
  149. message = client._create_message('oadrPoll', ven_id='ven123')
  150. tree = etree.fromstring(message.encode('utf-8'))
  151. messaging._verify_replay_protect(tree)
  152. with pytest.raises(ValueError) as err:
  153. messaging._verify_replay_protect(tree)
  154. assert str(err.value) == 'This combination of timestamp and nonce was already used.'
  155. def test_replay_protect_missing_nonce(caplog):
  156. client = OpenADRClient(ven_name='myven',
  157. vtn_url=f'https://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b',
  158. cert=VEN_CERT,
  159. key=VEN_KEY,
  160. vtn_fingerprint='EE:44:C5:78:7E:4B:B8:DC:84:1F')
  161. message = client._create_message('oadrPoll', ven_id='ven123')
  162. message = re.sub('<dsp:nonce>.*?</dsp:nonce>', '', message)
  163. tree = etree.fromstring(message.encode('utf-8'))
  164. with pytest.raises(ValueError) as err:
  165. messaging._verify_replay_protect(tree)
  166. assert str(err.value) == "Missing 'nonce' element in ReplayProtect in incoming message."
  167. def test_replay_protect_malformed_nonce(caplog):
  168. client = OpenADRClient(ven_name='myven',
  169. vtn_url=f'https://localhost:{SERVER_PORT}/OpenADR2/Simple/2.0b',
  170. cert=VEN_CERT,
  171. key=VEN_KEY,
  172. vtn_fingerprint='EE:44:C5:78:7E:4B:B8:DC:84:1F')
  173. message = client._create_message('oadrPoll', ven_id='ven123')
  174. message = re.sub('<dsp:timestamp>.*?</dsp:timestamp>', '', message)
  175. tree = etree.fromstring(message.encode('utf-8'))
  176. with pytest.raises(ValueError) as err:
  177. messaging._verify_replay_protect(tree)
  178. assert str(err.value) == "Missing or malformed ReplayProtect element in the message signature."
  179. message = re.sub('<dsp:ReplayProtect>.*?</dsp:ReplayProtect>', '', message)
  180. tree = etree.fromstring(message.encode('utf-8'))
  181. with pytest.raises(ValueError) as err:
  182. messaging._verify_replay_protect(tree)
  183. assert str(err.value) == "Missing or malformed ReplayProtect element in the message signature."
  184. ##########################################################################################
  185. SERVER_PORT = 8001
  186. VEN_NAME = 'myven'
  187. VEN_ID = '1234abcd'
  188. VTN_ID = "TestVTN"
  189. VEN_CERT = os.path.join(os.path.dirname(os.path.dirname(__file__)), "certificates", "dummy_ven.crt")
  190. VEN_KEY = os.path.join(os.path.dirname(os.path.dirname(__file__)), "certificates", "dummy_ven.key")
  191. VTN_CERT = os.path.join(os.path.dirname(os.path.dirname(__file__)), "certificates", "dummy_vtn.crt")
  192. VTN_KEY = os.path.join(os.path.dirname(os.path.dirname(__file__)), "certificates", "dummy_vtn.key")
  193. CA_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), "certificates", "dummy_ca.crt")
  194. async def _on_create_party_registration(payload):
  195. registration_id = generate_id()
  196. payload = {'response': {'response_code': 200,
  197. 'response_description': 'OK',
  198. 'request_id': payload['request_id']},
  199. 'ven_id': VEN_ID,
  200. 'registration_id': registration_id,
  201. 'profiles': [{'profile_name': '2.0b',
  202. 'transports': {'transport_name': 'simpleHttp'}}],
  203. 'requested_oadr_poll_freq': timedelta(seconds=1)}
  204. return 'oadrCreatedPartyRegistration', payload
  205. async def _client_on_event(event):
  206. pass
  207. async def _client_on_report(report):
  208. pass
  209. def fingerprint_lookup(ven_id):
  210. return "6B:C8:4E:47:15:AA:30:EB:CE:0E"
  211. @pytest.fixture
  212. async def start_server():
  213. server = OpenADRServer(vtn_id=VTN_ID, http_port=SERVER_PORT)
  214. server.add_handler('on_create_party_registration', _on_create_party_registration)
  215. await server.run_async()
  216. yield
  217. await server.stop()
  218. @pytest.fixture
  219. async def start_server_with_signatures():
  220. server = OpenADRServer(vtn_id=VTN_ID,
  221. cert=VTN_CERT,
  222. key=VTN_KEY,
  223. http_cert=VTN_CERT,
  224. http_key=VTN_KEY,
  225. http_ca_file=CA_FILE,
  226. http_port=SERVER_PORT,
  227. fingerprint_lookup=fingerprint_lookup)
  228. server.add_handler('on_create_party_registration', _on_create_party_registration)
  229. await server.run_async()
  230. yield
  231. await server.stop()