holado 0.2.7__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of holado might be problematic. Click here for more details.

Files changed (118) hide show
  1. holado/common/handlers/undefined.py +7 -1
  2. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/METADATA +7 -1
  3. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/RECORD +116 -107
  4. holado_ais/ais/ais_messages.py +97 -6
  5. holado_ais/tests/behave/steps/ais/ais_manager_steps.py +1 -1
  6. holado_ais/tests/behave/steps/ais/ais_messages_steps.py +45 -6
  7. holado_binary/ipc/bit_series.py +3 -3
  8. holado_binary/tests/behave/steps/ipc/binary_steps.py +1 -1
  9. holado_binary/tests/behave/steps/ipc/bit_series_steps.py +4 -3
  10. holado_context/tests/behave/steps/private/common/context_steps.py +1 -1
  11. holado_core/common/resource/persisted_data_manager.py +13 -16
  12. holado_core/common/resource/resource_manager.py +10 -10
  13. holado_core/common/tables/converters/table_converter.py +47 -9
  14. holado_core/common/tables/table.py +2 -2
  15. holado_core/common/tables/table_manager.py +6 -7
  16. holado_core/common/tables/table_with_header.py +6 -0
  17. holado_core/common/tools/string_tools.py +9 -0
  18. holado_core/tests/behave/steps/common/common_steps.py +2 -1
  19. holado_core/tests/behave/steps/common/config_steps.py +1 -1
  20. holado_core/tests/behave/steps/common/resource_steps.py +1 -1
  21. holado_core/tests/behave/steps/common/tables_steps.py +27 -4
  22. holado_data/data/generator/generator_manager.py +39 -0
  23. holado_data/tests/behave/steps/data/generator_steps.py +1 -1
  24. holado_data/tests/behave/steps/tools/utils_steps.py +1 -2
  25. holado_db/tests/behave/steps/tools/db/db_client_steps.py +1 -1
  26. holado_db/tests/behave/steps/tools/db/postgresql_client_steps.py +1 -1
  27. holado_db/tests/behave/steps/tools/db/sqlite_client_steps.py +1 -1
  28. holado_db/tools/db/clients/base/db_client.py +81 -28
  29. holado_db/tools/db/clients/postgresql/postgresql_client.py +17 -7
  30. holado_db/tools/db/query/base/query_builder.py +58 -7
  31. holado_db/tools/db/query/pypika/pypika_query_builder.py +73 -21
  32. holado_docker/tests/behave/steps/tools/docker_steps.py +1 -1
  33. holado_grpc/tests/behave/steps/api/grpc_client_steps.py +1 -1
  34. holado_grpc/tests/behave/steps/private/api/grpc_steps.py +1 -1
  35. holado_helper/script/action.py +16 -7
  36. holado_json/tests/behave/steps/ipc/json_steps.py +1 -1
  37. holado_keycloak/tests/behave/steps/tools/keycloak_client_steps.py +1 -1
  38. holado_multitask/tests/behave/steps/multiprocessing_steps.py +1 -1
  39. holado_multitask/tests/behave/steps/multithreading_steps.py +1 -1
  40. holado_protobuf/ipc/protobuf/types/google/protobuf.py +1 -1
  41. holado_protobuf/tests/behave/steps/ipc/protobuf_steps.py +1 -1
  42. holado_python/common/tools/datetime.py +31 -12
  43. holado_python/standard_library/socket/blocking_socket.py +112 -42
  44. holado_python/standard_library/socket/echo_server.py +4 -3
  45. holado_python/standard_library/socket/message_socket.py +69 -22
  46. holado_python/standard_library/socket/non_blocking_socket.py +65 -67
  47. holado_python/standard_library/socket/socket.py +272 -32
  48. holado_python/standard_library/ssl/resources/certificates/NOTES.txt +1 -1
  49. holado_python/standard_library/ssl/resources/certificates/rootCACert.pem +24 -0
  50. holado_python/standard_library/ssl/resources/certificates/tcpbin.crt +21 -0
  51. holado_python/standard_library/ssl/resources/certificates/tcpbin.key +28 -0
  52. holado_python/standard_library/ssl/ssl.py +138 -21
  53. holado_python/tests/behave/steps/convert_steps.py +1 -1
  54. holado_python/tests/behave/steps/iterable_steps.py +1 -1
  55. holado_python/tests/behave/steps/standard_library/csv_steps.py +1 -1
  56. holado_python/tests/behave/steps/standard_library/datetime_steps.py +1 -1
  57. holado_python/tests/behave/steps/standard_library/hashlib_steps.py +1 -1
  58. holado_python/tests/behave/steps/standard_library/multiprocessing_steps.py +1 -1
  59. holado_python/tests/behave/steps/standard_library/queue_steps.py +1 -1
  60. holado_python/tests/behave/steps/standard_library/socket_steps.py +147 -21
  61. holado_python/tests/behave/steps/standard_library/ssl_steps.py +87 -16
  62. holado_rabbitmq/tests/behave/steps/tools/rabbitmq_client_steps.py +48 -20
  63. holado_rabbitmq/tests/behave/steps/tools/rabbitmq_server_steps.py +1 -1
  64. holado_rabbitmq/tools/rabbitmq/rabbitmq_client.py +19 -13
  65. holado_rabbitmq/tools/rabbitmq/rabbitmq_manager.py +2 -29
  66. holado_redis/tests/behave/steps/tools/redis_client_steps.py +1 -1
  67. holado_rest/tests/behave/steps/api/rest_client_steps.py +1 -1
  68. holado_rest/tests/behave/steps/private/api/rest_steps.py +1 -1
  69. holado_s3/tests/behave/steps/private/tools/s3_steps.py +1 -1
  70. holado_s3/tests/behave/steps/tools/s3_client_steps.py +1 -1
  71. holado_s3/tests/behave/steps/tools/s3_server_steps.py +1 -1
  72. holado_scripting/tests/behave/steps/common/tools/variable_convert_steps.py +3 -2
  73. holado_scripting/tests/behave/steps/common/tools/variable_new_steps.py +1 -1
  74. holado_scripting/tests/behave/steps/common/tools/variable_steps.py +1 -1
  75. holado_scripting/tests/behave/steps/common/tools/variable_verify_steps.py +1 -1
  76. holado_scripting/tests/behave/steps/scenario/function_steps.py +1 -1
  77. holado_scripting/tests/behave/steps/scenario/if_steps.py +1 -1
  78. holado_scripting/tests/behave/steps/scenario/loop_steps.py +1 -1
  79. holado_scripting/text/interpreter/functions/function_apply_function.py +60 -0
  80. holado_scripting/text/interpreter/functions/function_to_string.py +50 -0
  81. holado_scripting/text/interpreter/text_interpreter.py +4 -0
  82. holado_sftp/tests/behave/steps/private/tools/sftp_steps.py +1 -1
  83. holado_sftp/tests/behave/steps/tools/sftp_client_steps.py +1 -1
  84. holado_sftp/tests/behave/steps/tools/sftp_server_steps.py +1 -1
  85. holado_swagger/tests/behave/steps/swagger_hub/mockserver_steps.py +1 -1
  86. holado_system/system/command/command.py +14 -9
  87. holado_system/tests/behave/steps/system/commands_steps.py +1 -1
  88. holado_system/tests/behave/steps/system/file_steps.py +1 -1
  89. holado_system/tests/behave/steps/system/system_steps.py +1 -1
  90. holado_test/scenario/step_tools.py +5 -4
  91. holado_test/scenario/tester_tools.py +6 -3
  92. holado_test/tests/behave/steps/scenario/exception_steps.py +1 -1
  93. holado_test/tests/behave/steps/scenario/scenario_steps.py +1 -1
  94. holado_test/tests/behave/steps/scenario/tester_steps.py +4 -4
  95. holado_value/common/tables/converters/value_table_converter.py +52 -8
  96. holado_value/common/tables/value_table_cell.py +5 -0
  97. holado_value/common/tables/value_table_manager.py +0 -10
  98. holado_value/common/tables/value_table_row.py +0 -1
  99. holado_value/common/tools/value.py +5 -1
  100. holado_ws/tests/behave/steps/api/web_service_steps.py +1 -1
  101. holado_yaml/tests/behave/steps/yaml_steps.py +1 -1
  102. holado_yaml/yaml/yaml_manager.py +2 -2
  103. test_holado/features/NonReg/common/tables/table.feature +30 -24
  104. test_holado/features/NonReg/holado_ais/ais_message-bitarray_to_nmea.feature +1 -1
  105. test_holado/features/NonReg/holado_python/standard_library/socket/local_echo_server/socket_reset.feature +191 -0
  106. test_holado/features/NonReg/holado_python/standard_library/{socket_with_ssl.feature → socket/local_echo_server/socket_with_tls_and_verify.feature} +126 -27
  107. test_holado/features/NonReg/holado_python/standard_library/socket/local_echo_server/socket_with_tls_without_verify.feature +299 -0
  108. test_holado/features/NonReg/holado_python/standard_library/{socket.feature → socket/local_echo_server/socket_without_tls.feature} +63 -1
  109. test_holado/features/NonReg/holado_python/standard_library/socket/tcpbin.com/socket_with_mtls.feature +214 -0
  110. test_holado/features/NonReg/holado_python/standard_library/socket/tcpbin.com/socket_with_tls.feature +184 -0
  111. test_holado/features/NonReg/holado_python/standard_library/socket/tcpbin.com/socket_without_tls.feature +169 -0
  112. test_holado/features/NonReg/tools/RabbitMQ.feature +9 -9
  113. test_holado/features/NonReg/tools/RabbitMQ_steps.feature +8 -8
  114. test_holado/logging.conf +5 -3
  115. holado_core/common/transport/__init__.py +0 -0
  116. holado_core/common/transport/crc.py +0 -40
  117. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/WHEEL +0 -0
  118. {holado-0.2.7.dist-info → holado-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -13,12 +13,20 @@
13
13
 
14
14
  import logging
15
15
  import socket
16
- from _socket import SHUT_RDWR
16
+ from _socket import SHUT_RDWR, SOCK_STREAM, error
17
17
  from holado.common.handlers.object import DeleteableObject
18
18
  import abc
19
19
  from holado_core.common.exceptions.functional_exception import FunctionalException
20
20
  from holado_core.common.tools.tools import Tools
21
21
  from holado_python.standard_library.ssl.ssl import SslManager
22
+ import threading
23
+ import types
24
+ from abc import abstractmethod
25
+ from holado.common.handlers.undefined import undefined_argument
26
+ import copy
27
+ from holado_python.standard_library.typing import Typing
28
+ import ssl
29
+ import select
22
30
 
23
31
  logger = logging.getLogger(__name__)
24
32
 
@@ -31,14 +39,83 @@ class Socket(DeleteableObject):
31
39
  """
32
40
  __metaclass__ = abc.ABCMeta
33
41
 
34
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
42
+ @classmethod
43
+ def create(cls, name, internal_socket):
44
+ res = cls.__new__(cls, name=name)
45
+ res.__init__(name=name)
46
+ res._set_internal_socket(internal_socket)
47
+ return res
48
+
49
+ @classmethod
50
+ def create_connection(cls, address, timeout=undefined_argument,
51
+ source_address=None, do_connect=True):
52
+ """Connect to *address* and return the socket object.
53
+
54
+ Convenience function. Connect to *address* (a 2-tuple ``(host,
55
+ port)``) and return the socket object. Passing the optional
56
+ *timeout* parameter will set the timeout on the socket instance
57
+ before attempting to connect. If no *timeout* is supplied, the
58
+ global default timeout setting returned by :func:`getdefaulttimeout`
59
+ is used. If *source_address* is set it must be a tuple of (host, port)
60
+ for the socket to bind as a source address before making the connection.
61
+ A host of '' or port 0 tells the OS to use the default.
62
+
63
+ Note: Implementation is a copy of socket.create_connection, but allowing to not connect.
64
+ If do_connect==True, implementation is identical to socket.create_connection.
65
+ """
66
+
67
+ host, port = address
68
+ err = None
69
+ for res in socket.getaddrinfo(host, port, 0, SOCK_STREAM):
70
+ af, socktype, proto, canonname, sa = res # @UnusedVariable
71
+ sock = None
72
+ try:
73
+ sock = socket.socket(af, socktype, proto)
74
+ if timeout is not undefined_argument:
75
+ sock.settimeout(timeout)
76
+ if source_address:
77
+ sock.bind(source_address)
78
+ if do_connect:
79
+ sock.connect(sa)
80
+ # Break explicitly a reference cycle
81
+ err = None
82
+ return sock
83
+
84
+ except error as _:
85
+ err = _
86
+ if sock is not None:
87
+ sock.close()
88
+
89
+ if err is not None:
90
+ try:
91
+ raise err
92
+ finally:
93
+ # Break explicitly a reference cycle
94
+ err = None
95
+ else:
96
+ raise error("getaddrinfo returns an empty list")
97
+
98
+ def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
99
+ """Socket constructor
100
+ @param name: Socket name
101
+ @param create_ipv4_socket_kwargs: arguments to create an IPv4 socket
102
+ @param idle_sleep_delay: delay to sleep when socket is idle (used in some clients/servers to control CPU resource impact ; default: 0.01 s)
103
+ """
35
104
  if name is None and create_ipv4_socket_kwargs is not None:
36
105
  if not set(create_ipv4_socket_kwargs.keys()).issuperset({'host', 'port'}):
37
106
  raise FunctionalException(f"Parameters 'host' and 'port' must be defined")
38
- name = f"{create_ipv4_socket_kwargs['host']}:{create_ipv4_socket_kwargs['port']}"
107
+ name = f"{Typing.get_object_class_name(self)}({create_ipv4_socket_kwargs['host']}:{create_ipv4_socket_kwargs['port']})"
39
108
 
40
109
  super().__init__(name)
41
110
  self.__socket = None
111
+ self.__is_started = False
112
+ self.__start_thread = None
113
+ self.__idle_sleep_delay = idle_sleep_delay if idle_sleep_delay is not undefined_argument else 0.01
114
+
115
+ # SSL management
116
+ self.__ssl_kwargs_tuple = None
117
+ self.__ssl_context = None
118
+ self.__is_ssl_handshake_done = False
42
119
 
43
120
  if create_ipv4_socket_kwargs is not None:
44
121
  self.create_ipv4_socket(**create_ipv4_socket_kwargs)
@@ -56,8 +133,37 @@ class Socket(DeleteableObject):
56
133
  def internal_socket(self) -> socket.socket:
57
134
  return self.__socket
58
135
 
59
- def _set_internal_socket(self, socket):
136
+ @property
137
+ def is_started(self):
138
+ return self.__is_started
139
+
140
+ @property
141
+ def is_with_ssl(self):
142
+ return self.__ssl_context is not None
143
+
144
+ @property
145
+ def _ssl_kwargs(self):
146
+ return copy.copy(self.__ssl_kwargs_tuple[0]) if self.__ssl_kwargs_tuple and self.__ssl_kwargs_tuple[0] is not None else None
147
+
148
+ @property
149
+ def _ssl_context_kwargs(self):
150
+ return copy.copy(self.__ssl_kwargs_tuple[1]) if self.__ssl_kwargs_tuple and self.__ssl_kwargs_tuple[1] is not None else None
151
+
152
+ @property
153
+ def _ssl_wrap_socket_kwargs(self):
154
+ return copy.copy(self.__ssl_kwargs_tuple[2]) if self.__ssl_kwargs_tuple and self.__ssl_kwargs_tuple[2] is not None else None
155
+
156
+ @property
157
+ def _ssl_context(self):
158
+ return self.__ssl_context
159
+
160
+ @property
161
+ def _idle_sleep_delay(self):
162
+ return self.__idle_sleep_delay
163
+
164
+ def _set_internal_socket(self, socket, is_ssl_handshake_done_on_connect=True):
60
165
  self.__socket = socket
166
+ self.__is_ssl_handshake_done = is_ssl_handshake_done_on_connect
61
167
 
62
168
  @abc.abstractmethod
63
169
  def create_ipv4_socket(self, host, port, **kwargs):
@@ -68,15 +174,43 @@ class Socket(DeleteableObject):
68
174
  try:
69
175
  self.__socket.shutdown(SHUT_RDWR)
70
176
  except OSError as exc:
71
- if 'Errno 107' in str(exc):
72
- logger.info(f"Got following error on socket shutdown (known to appear sometimes at shutdown): {exc}")
177
+ if 'Errno 9' in str(exc):
178
+ logger.info(f"Got following error on socket shutdown (known to appear when connection is not open correctly or already closed): {exc}")
179
+ elif 'Errno 107' in str(exc):
180
+ logger.info(f"Got following error on socket shutdown (known to appear sometimes during shutdown): {exc}")
73
181
  else:
74
182
  raise exc
75
- self.__socket.close()
76
- self.__socket = None
183
+ finally:
184
+ self.__socket.close()
185
+ self.__socket = None
186
+
187
+ @abstractmethod
188
+ def start(self, *, read_bufsize=1024, read_kwargs=None, write_kwargs=None):
189
+ raise NotImplementedError()
190
+
191
+ def _start_thread(self, thread):
192
+ # Start thread
193
+ self.__start_thread = thread
194
+ self.__start_thread.start()
195
+ self.__is_started = True
196
+
197
+ if Tools.do_log(logger, logging.DEBUG):
198
+ logger.debug(f"[{self.name}] Started")
199
+
200
+ def stop(self):
201
+ if self.__start_thread is not None:
202
+ if self.__start_thread.is_interruptable:
203
+ self.__start_thread.interrupt()
204
+ self.__start_thread.join()
205
+ self.__start_thread = None
206
+ self.__is_started = False
207
+
208
+ if Tools.do_log(logger, logging.DEBUG):
209
+ logger.debug(f"[{self.name}] Stopped")
77
210
 
78
211
  def read(self, bufsize=1024, **kwargs):
79
- res = self.internal_socket.recv(bufsize, **kwargs)
212
+ flags = kwargs.get('flags', 0)
213
+ res = self.internal_socket.recv(bufsize, flags)
80
214
  if logger.isEnabledFor(logging.DEBUG):
81
215
  logger.debug(f"[{self.name}] Received [{res}] (type: {type(res)})")
82
216
  return res
@@ -93,14 +227,50 @@ class Socket(DeleteableObject):
93
227
  logger.debug(f"[{self.name}] Sent {res}/{len(data_bytes)} bytes of [{data_bytes}] (type: {type(data_bytes)})")
94
228
  return res
95
229
 
96
- def _new_ssl_context_if_required(self, server_side=False, **socket_kwargs):
97
- """Return a SSLContext if required, and the remaining socket kwargs.
230
+ def _extract_ssl_kwargs(self, socket_kwargs):
231
+ """Pop from socket kwargs: global ssl kwargs, SSLContext kwargs, and wrap_socket kwargs.
98
232
  """
99
- res = None
233
+ ssl_kwargs, context_kwargs, wrap_socket_kwargs = None, {}, {}
100
234
  if Tools.has_sub_kwargs(socket_kwargs, 'ssl.'):
101
235
  ssl_kwargs = Tools.pop_sub_kwargs(socket_kwargs, 'ssl.')
102
- res = SslManager.new_ssl_context(server_side=server_side, flat_kwargs=ssl_kwargs)
103
- return res, socket_kwargs
236
+ if Tools.has_sub_kwargs(ssl_kwargs, 'context.'):
237
+ context_kwargs = Tools.pop_sub_kwargs(ssl_kwargs, 'context.')
238
+ if Tools.has_sub_kwargs(ssl_kwargs, 'wrap_socket.'):
239
+ wrap_socket_kwargs = Tools.pop_sub_kwargs(ssl_kwargs, 'wrap_socket.')
240
+ self.__ssl_kwargs_tuple = (ssl_kwargs, context_kwargs, wrap_socket_kwargs)
241
+ return socket_kwargs
242
+
243
+ def _new_ssl_context_if_required(self, server_side=False, **kwargs):
244
+ """Return the remaining socket kwargs after ssl kwargs extraction.
245
+ """
246
+ res = self._extract_ssl_kwargs(kwargs)
247
+
248
+ if self._ssl_kwargs is not None:
249
+ self.__ssl_context = SslManager.new_ssl_context(server_side=server_side, ssl_kwargs=self._ssl_kwargs, context_kwargs=self._ssl_context_kwargs)
250
+
251
+ return res
252
+
253
+ def do_ssl_handshake(self):
254
+ if self.is_with_ssl:
255
+ if self.internal_socket.getblocking():
256
+ self.internal_socket.do_handshake()
257
+ # self.internal_socket.do_handshake(block=True)
258
+ else:
259
+ while True:
260
+ try:
261
+ self.internal_socket.do_handshake()
262
+ break
263
+ except ssl.SSLWantReadError:
264
+ select.select([self.internal_socket], [], [])
265
+ except ssl.SSLWantWriteError:
266
+ select.select([], [self.internal_socket], [])
267
+
268
+
269
+
270
+ def ensure_ssl_handshake_is_done(self):
271
+ if self.is_with_ssl and not self.__is_ssl_handshake_done:
272
+ self.do_ssl_handshake()
273
+ self.__is_ssl_handshake_done = True
104
274
 
105
275
 
106
276
  class SocketClient(Socket):
@@ -109,10 +279,91 @@ class SocketClient(Socket):
109
279
  """
110
280
  __metaclass__ = abc.ABCMeta
111
281
 
112
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
113
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
282
+ def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument, do_run_with_recv=True, do_run_with_send=True):
283
+ """Socket client constructor
284
+ @param name: Socket client name
285
+ @param create_ipv4_socket_kwargs: Arguments to create an IPv4 socket
286
+ @param idle_sleep_delay: delay to sleep when socket is idle (used only in some clients to control CPU resource impact ; default: 0.01 s)
287
+ @param do_run_with_recv: Define if recv is done in run process when client is started
288
+ @param do_run_with_send: Define if send is done in run process when client is started
289
+ """
290
+ # Data used in start processing
291
+ self.__data_lock = threading.Lock()
292
+ self.__data = types.SimpleNamespace(
293
+ in_bytes = b'',
294
+ out_bytes = b'',
295
+ )
296
+ self.__with_recv = do_run_with_recv
297
+ self.__with_send = do_run_with_send
298
+
299
+ # Note: super().__init__ must be done after self.__data & others initialization since it can execute create_ipv4_socket_kwargs method
300
+ super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
114
301
 
115
-
302
+ @property
303
+ def is_run_with_recv(self):
304
+ return self.__with_recv
305
+
306
+ @property
307
+ def is_run_with_send(self):
308
+ return self.__with_send
309
+
310
+ @property
311
+ def _data_lock(self):
312
+ return self.__data_lock
313
+
314
+ @property
315
+ def _data(self):
316
+ return self.__data
317
+
318
+ def _delete_object(self):
319
+ self.stop()
320
+ super()._delete_object()
321
+
322
+ def _start_thread(self, thread):
323
+ # Ensure SSL handshake is done before starting to receive and send data
324
+ self.ensure_ssl_handshake_is_done()
325
+
326
+ # Start thread
327
+ super()._start_thread(thread)
328
+
329
+ @property
330
+ def received_data(self):
331
+ with self.__data_lock:
332
+ res = copy.copy(self.__data.in_bytes)
333
+ return res
334
+
335
+ @property
336
+ def received_data_size(self):
337
+ with self.__data_lock:
338
+ res = len(self.__data.in_bytes)
339
+ return res
340
+
341
+ def reset_received_data(self):
342
+ with self.__data_lock:
343
+ self.__data.in_bytes = b''
344
+
345
+ def read(self, bufsize=1024, **kwargs):
346
+ if self.is_started and self.is_run_with_recv:
347
+ with self.__data_lock:
348
+ if self.__data.in_bytes:
349
+ res = self.__data.in_bytes[:bufsize]
350
+ self.__data.in_bytes = self.__data.in_bytes[bufsize:]
351
+ else:
352
+ res = b''
353
+ if logger.isEnabledFor(logging.DEBUG):
354
+ logger.debug(f"[{self.name}] Read data from received data: [{res}] (type: {type(res)} ; remaining data: {len(self.__data.in_bytes)})")
355
+ return res
356
+ else:
357
+ return super().read(bufsize=bufsize, **kwargs)
358
+
359
+ def write(self, data_bytes, loop_until_all_is_sent=True, **kwargs):
360
+ if self.is_started and self.is_run_with_send:
361
+ with self.__data_lock:
362
+ self.__data.out_bytes += data_bytes
363
+ if logger.isEnabledFor(logging.DEBUG):
364
+ logger.debug(f"[{self.name}] Added data in data to send (total: {len(self.__data.out_bytes)})")
365
+ else:
366
+ return super().write(data_bytes, loop_until_all_is_sent=loop_until_all_is_sent, **kwargs)
116
367
 
117
368
 
118
369
  class SocketServer(Socket):
@@ -121,27 +372,16 @@ class SocketServer(Socket):
121
372
  """
122
373
  __metaclass__ = abc.ABCMeta
123
374
 
124
- def __init__(self, *, name=None, create_ipv4_socket_kwargs=None):
125
- super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs)
375
+ def __init__(self, *, name=None, create_ipv4_socket_kwargs=None, idle_sleep_delay=undefined_argument):
376
+ super().__init__(name=name, create_ipv4_socket_kwargs=create_ipv4_socket_kwargs, idle_sleep_delay=idle_sleep_delay)
126
377
 
127
378
  def accept(self):
128
379
  conn, addr = self.internal_socket.accept()
129
- res = Socket(name=f"[{self.name}] Connection with {addr}")
130
- res._set_internal_socket(conn)
380
+ res = Socket.create(name=f"[{self.name}] Connection with {addr}", internal_socket=conn)
131
381
  return res, addr
132
382
 
133
383
  @abc.abstractmethod
134
- def start(self, read_bufsize=1024, *, read_kwargs=None, write_kwargs=None):
135
- """Start server.
136
- """
137
- raise NotImplementedError()
138
-
139
- @abc.abstractmethod
140
- def stop(self):
141
- raise NotImplementedError()
142
-
143
- @abc.abstractmethod
144
- def _process_data(self, data):
384
+ def _process_received_data(self, data):
145
385
  raise NotImplementedError()
146
386
 
147
387
 
@@ -1,4 +1,4 @@
1
- localhost.ct and localhost.key were generated with command:
1
+ localhost.crt and localhost.key were generated with command:
2
2
  openssl req -x509 -out localhost.crt -keyout localhost.key \
3
3
  -newkey rsa:2048 -nodes -sha256 \
4
4
  -subj '/CN=localhost' -extensions EXT -config <( \
@@ -0,0 +1,24 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIID7jCCAtagAwIBAgIJAJNpPsX/1B+fMA0GCSqGSIb3DQEBCwUAMIGLMQswCQYD
3
+ VQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzAN
4
+ BgNVBAoMBnRjcGJpbjEMMAoGA1UECwwDb3BzMRMwEQYDVQQDDAp0Y3BiaW4uY29t
5
+ MSMwIQYJKoZIhvcNAQkBFhRoYXJyeWJhZ2RpQGdtYWlsLmNvbTAeFw0xOTA1MDUw
6
+ NjI3NTlaFw0yOTA1MDIwNjI3NTlaMIGLMQswCQYDVQQGEwJVUzELMAkGA1UECAwC
7
+ Q0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDzANBgNVBAoMBnRjcGJpbjEMMAoG
8
+ A1UECwwDb3BzMRMwEQYDVQQDDAp0Y3BiaW4uY29tMSMwIQYJKoZIhvcNAQkBFhRo
9
+ YXJyeWJhZ2RpQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
10
+ ggEBAK0LTwoX+/d/QKJuIuvUw2gUBGwjeX8DM3OqagcmZutlq9VfSW1pkzNhCpFU
11
+ 1JQh/bJ3P97VTZn75H2zL4BQnAXaPSRoi0wOFsoEhzbIOAFELPq2TMoY9lvOLO9n
12
+ 4yt4Pz7AbFLhPDeltqb91S/18hN1YmVTDEuMCz4OGnJOf5NbmuVwmjMcDWiOwmjp
13
+ dBYlwCMn/TRB3wUPSwafLef9EIJfm4uL0ruH3Rlhj8Lktt0YTvrA7M6URMcgFqYG
14
+ wNNsYa9X88Js1nxlryvTputbVf6zL35S7CvTGc6fft/pfdSAu2KcmOd+C0dew1GI
15
+ daM9tqTiqi6Tg+IA2t4eaA3tR2MCAwEAAaNTMFEwHQYDVR0OBBYEFOLuMowBSAZf
16
+ V5v82LmlaIIOvU/DMB8GA1UdIwQYMBaAFOLuMowBSAZfV5v82LmlaIIOvU/DMA8G
17
+ A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEj3o18u8FQDrQu89Etk
18
+ 4sQgiZxtQ0C3OglCXP9kRGSBt8Ymd/2bmpGK2TsEpACkfpJLaFRVPek2302YQ8mc
19
+ oj63wPTDYx1P/BSH+dPvDUXI9SePXcrFAPq9YemteQHE+WZ/PFe0wAq1ehziWgqQ
20
+ OiMb1wydDbaRZF6jPPMW7cHCiUYceLBRO0ZTe9CsUPYw6A78QZnO6P7Cuc1SwkNv
21
+ uejCGTfv5HP72/Jm24ifAMOoWXALmvIIAnOVTwpPEX2nEnr9H1S8Psi3+mqkGyC1
22
+ RQXprzHkflCKHxjiNfNguHeC3x6l0LGWEShcemyDWCsTgCYNX6RMTWWcug3Vb/70
23
+ BSs=
24
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDZTCCAk2gAwIBAgIBKjANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMCVVMx
3
+ CzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMQ8wDQYDVQQKDAZ0
4
+ Y3BiaW4xDDAKBgNVBAsMA29wczETMBEGA1UEAwwKdGNwYmluLmNvbTEjMCEGCSqG
5
+ SIb3DQEJARYUaGFycnliYWdkaUBnbWFpbC5jb20wHhcNMjUwNTI4MTMxODIwWhcN
6
+ MjUwNTI5MTMxODIwWjAcMRowGAYDVQQDDBF0Y3BiaW4uY29tLWNsaWVudDCCASIw
7
+ DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANvo9G/86es6qoCHTum0RAoqAAiY
8
+ qnOCLgXmamgfQgbfYoBIkNTBBVzsZyoZIY4QuuO8E07VmbrnH23czCSAO5akPb7D
9
+ teotATqDf4My0Ybht62nLoxhHBb9jrzSgv4+04gy/W6wu5diyfeY8BNopHkUkmEN
10
+ WhmGkP9s9npUGaEfEeasuiB1JMkAEOWnw+kHqE3Us+4Av4Mht6srswst3B90WCIV
11
+ Rry48gRJS4qZoAk1dwZHIcYSOfMJ8Pu69wS1qjRwRPLc3dfnUs2E3dAx1FDxDBkK
12
+ eQ73VUm64A5AXLYRk3PvgPwbcgerkLAnYAb5o8YJmyufGXKQNhmZxcCt+t0CAwEA
13
+ AaNCMEAwHQYDVR0OBBYEFDgjXXI6juiInalFSZEZM6BcEW9UMB8GA1UdIwQYMBaA
14
+ FOLuMowBSAZfV5v82LmlaIIOvU/DMA0GCSqGSIb3DQEBCwUAA4IBAQAu6SJ3OT6D
15
+ fhjfg4wRMHZVsaYSF6Kwe0RNt6izRkv5MUGi6Ew0SK1Mm+qxE51G3aaVCE4G+P1e
16
+ ACOiBL55saWDJtdng2VTKhUsFxIk2c/t+tuh7ttV7i0yGbaLGDP0N2FveBhKj/5d
17
+ SaPpAQvZ1rFhXpUU1jQr+xi5pP/iOPH02O8A8VgHyGebDVXs8iWMHjozOrRhgQVJ
18
+ YAl5lyYs+aj199p9ixwY3RPPWbUcggvAipv9bu6uEEIWMOMMERM2P7yFYaWJBcw5
19
+ A/Uqu2bBLmOu9/qmlEj2uQfTeVhnoT/bZBfmPcrL64tHjw7G5u0WuooPNTI23z28
20
+ 3ttcr+fDbDR4
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDb6PRv/OnrOqqA
3
+ h07ptEQKKgAImKpzgi4F5mpoH0IG32KASJDUwQVc7GcqGSGOELrjvBNO1Zm65x9t
4
+ 3MwkgDuWpD2+w7XqLQE6g3+DMtGG4betpy6MYRwW/Y680oL+PtOIMv1usLuXYsn3
5
+ mPATaKR5FJJhDVoZhpD/bPZ6VBmhHxHmrLogdSTJABDlp8PpB6hN1LPuAL+DIber
6
+ K7MLLdwfdFgiFUa8uPIESUuKmaAJNXcGRyHGEjnzCfD7uvcEtao0cETy3N3X51LN
7
+ hN3QMdRQ8QwZCnkO91VJuuAOQFy2EZNz74D8G3IHq5CwJ2AG+aPGCZsrnxlykDYZ
8
+ mcXArfrdAgMBAAECggEAe76UfcfloTY752M8ZonHl6iWqD+v+puQZkWILtsX/mIJ
9
+ PYKX7QBIkkd8rdXCafzEDY4xlzTe8qtHpjyOqyN1ZIk7LXNXlFSK0nBYem1INgwh
10
+ nZfru5aRheZcQah7ibG0unlm3riYdtFiMO9geKtzkaafz/kBcEemo/SepatZWK3m
11
+ DQX6okkpHh04GZNIO52K2QCaYpmj14fXIi2p5M4+KD+gmYbXhkmzNdcjz2l/wkqo
12
+ SziZf31nzuoHd6FuQ6vAsmXU/m6jW7Nll0wsxTm5Ynw5SoIrAbreJJmSSD8o2Shj
13
+ mvzWlurk1SgOhtoHP+AaVAn454x/Fn7+8u0f5ptNyQKBgQDvKc/3etCi22ntlgQM
14
+ dIwxbUPnmSvwejpMPOq3kMGTQWUJcKFinbPC28s/lJGzVjMsH5VisjiprCt3TXGN
15
+ XwmLNaUycWN3po9HyLSndAjHWM4aQ3yTOFKfLFEDUhKi8Dxy995KmHhgSSHd65qM
16
+ PvnPhITZWfKLkItCOmNEULxE8wKBgQDrZCji91ztgNyN3MHDGN2o854lAvWc1l6s
17
+ 4L8ZwU/RCNYyvMkdhadASzLwVC44q/6txGjTHGjnJPdkzV2u+WsPVWG2uCaD9PV1
18
+ 3L8wyG9sJ2YoJE5QcAQ+1J+rRN3fluzSKFiQNDpf7C5YqCPvk7C7lXFllGHM6FmF
19
+ hoYbyIb07wKBgQDKaF+qulViz0FqIwFQLT8NAcVrd7W5MyitpwyayLcbUkgZYioj
20
+ lQYzDuOH7swUtApg+GXsfpr39k9fC7rjg6BHIeKqu04MUHmIrjM+WTSoyd68WYtP
21
+ 6WX7cn0py0ccgScXwfFuvnV6P8qaz7Afq5iuaSAp9zcPqQhCx7mFcrKzwwKBgQCo
22
+ VH3woNgxd59BS4a8j8GjmmOTMCSYPayCkE3Yiyca4ujaa6qek/9guOX6exh6qnR7
23
+ qyMTJRPXh9XqnfnKsM5grrwrwFC6uKf32x5WMl+LxjkFp8DhQNmoXMC554uK4xED
24
+ 0JpUtSSxh+I0wDjCkKkn29y1uYCe2eF63RJ2N9ZavQKBgQCbHI/bMIZw/fsaijg3
25
+ 676mpQqFPiQdXTogw8UWzZBAvP6eQOnqQ9aw4tJt6OZWnwCDMwXrEYk3H8qisZl7
26
+ ou63o5t8bTrU4C6e1VIQ1XuQM4FEpx6/TFwBG2FXkgzGHUpHlR9+woxqvmQgfsiW
27
+ tEJ/TZv7bU6s9FRLTzMspp+tIA==
28
+ -----END PRIVATE KEY-----
@@ -13,13 +13,15 @@
13
13
 
14
14
  import logging
15
15
  import ssl
16
- from holado_core.common.exceptions.functional_exception import FunctionalException
17
16
  from holado_core.common.tools.tools import Tools
18
17
  from holado_json.ipc.json import set_object_attributes_with_json_dict
19
18
  from holado_core.common.exceptions.technical_exception import TechnicalException
20
19
  import os
21
20
  import copy
22
21
  from ssl import Purpose
22
+ from holado_system.system.command.command import Command, CommandStates
23
+ from holado.common.context.session_context import SessionContext
24
+ import json
23
25
 
24
26
  logger = logging.getLogger(__name__)
25
27
 
@@ -32,37 +34,67 @@ class SslManager(object):
32
34
  """
33
35
 
34
36
  @classmethod
35
- def new_ssl_context(cls, server_side=False, flat_kwargs=None):
37
+ def __get_path_manager(cls):
38
+ return SessionContext.instance().path_manager
39
+
40
+ @classmethod
41
+ def new_ssl_context(cls, server_side=False, ssl_kwargs=None, context_kwargs=None):
36
42
  res = None
37
43
 
38
- if flat_kwargs is None:
39
- flat_kwargs = {}
40
- kwargs = copy.copy(flat_kwargs)
44
+ if ssl_kwargs is None:
45
+ ssl_kwargs = {}
46
+ kwargs = copy.copy(ssl_kwargs)
41
47
 
42
48
  try:
43
49
  activate_ssl = kwargs.pop('activate', True)
44
50
  if activate_ssl:
51
+ if Tools.has_sub_kwargs(kwargs, 'create_default_context.'):
52
+ create_default_context_kwargs = Tools.pop_sub_kwargs(kwargs, 'create_default_context.')
53
+ else:
54
+ create_default_context_kwargs = {}
55
+
45
56
  purpose = Purpose.CLIENT_AUTH if server_side else Purpose.SERVER_AUTH
46
- res = ssl.create_default_context(purpose=purpose)
57
+ res = ssl.create_default_context(purpose=purpose, **create_default_context_kwargs)
58
+
59
+ # res.set_default_verify_paths()
47
60
 
48
- if Tools.has_sub_kwargs(kwargs, 'context.'):
49
- context_kwargs = Tools.pop_sub_kwargs(kwargs, 'context.')
61
+ if context_kwargs:
62
+ c_kwargs = copy.copy(context_kwargs)
50
63
 
51
- ciphers = context_kwargs.pop('ciphers', None)
64
+ ciphers = c_kwargs.pop('ciphers', None)
52
65
  if ciphers is not None:
66
+ if ciphers == 'OPENSSL_CIPHERS':
67
+ ciphers = SslManager.get_openssl_ciphers()
68
+ logger.debug(f"Using openssl ciphers: {ciphers}")
53
69
  res.set_ciphers(ciphers)
54
- if Tools.has_sub_kwargs(context_kwargs, 'load_cert_chain.'):
55
- load_cert_chain_kwargs = Tools.pop_sub_kwargs(context_kwargs, 'load_cert_chain.')
70
+ if Tools.has_sub_kwargs(c_kwargs, 'load_cert_chain.'):
71
+ load_cert_chain_kwargs = Tools.pop_sub_kwargs(c_kwargs, 'load_cert_chain.')
56
72
  res.load_cert_chain(**load_cert_chain_kwargs)
57
- if Tools.has_sub_kwargs(context_kwargs, 'load_verify_locations.'):
58
- load_verify_locations_kwargs = Tools.pop_sub_kwargs(context_kwargs, 'load_verify_locations.')
73
+ if Tools.has_sub_kwargs(c_kwargs, 'load_verify_locations.'):
74
+ load_verify_locations_kwargs = Tools.pop_sub_kwargs(c_kwargs, 'load_verify_locations.')
59
75
  res.load_verify_locations(**load_verify_locations_kwargs)
60
76
 
61
- # Set context attributes with remaining kwargs
62
- if len(context_kwargs) > 0:
63
- set_object_attributes_with_json_dict(res, context_kwargs)
77
+ # Set context attributes with remaining c_kwargs
78
+ if len(c_kwargs) > 0:
79
+ set_object_attributes_with_json_dict(res, c_kwargs)
80
+
81
+ logger.debug(f"Default verify paths: {ssl.get_default_verify_paths()})")
82
+ dvp = ssl.get_default_verify_paths()
83
+ if res.verify_mode != ssl.CERT_NONE and dvp.cafile is None and 'create_default_context.cafile' not in ssl_kwargs and 'load_verify_locations.cafile' not in context_kwargs:
84
+ msg_list = [f"No CA file is defined, it is not possible to verify certificates.",
85
+ f"Most common solutions:",
86
+ f" - Configure to not verify certificates, by adding/setting in table: | 'ssl.context.verify_mode' | ssl.CERT_NONE |",
87
+ f" - Be sure OpenSSL default CA file exists on system (configured path: '{dvp.openssl_cafile}'). Example of command (on Ubuntu): sudo ln -s /etc/ssl/certs/ca-certificates.crt {dvp.openssl_cafile}",
88
+ f" - Configure a CA file at context creation, by adding/setting in table: | 'ssl.create_default_context.cafile' | <path to CA file> |",
89
+ f" - Configure a CA file after context creation, by adding/setting in table: | 'ssl.context.load_verify_locations.cafile' | <path to CA file> |",
90
+ f"Note: step 'Given CACERTS_PATH = CA certs file path (from certifi package)' can be used to get the path to CA file coming with 'certifi' package that is supposed to be installed when using ssl module",
91
+ ]
92
+ raise TechnicalException("\n".join(msg_list))
93
+ logger.debug(f"Loaded CA certificates: {res.get_ca_certs()})")
94
+ logger.debug(f"Loaded ciphers: {res.get_ciphers()})")
95
+
64
96
  except Exception as exc:
65
- msg = f"Failed to create SSL context with parameters {flat_kwargs}: {exc}"
97
+ msg = f"Failed to create SSL context with parameters ({ssl_kwargs}, {context_kwargs}): {exc}"
66
98
  logger.error(msg)
67
99
  raise TechnicalException(msg) from exc
68
100
 
@@ -73,9 +105,94 @@ class SslManager(object):
73
105
  return res
74
106
 
75
107
  @classmethod
76
- def get_localhost_certificate(cls):
108
+ def get_openssl_ciphers(cls):
109
+ import subprocess
110
+ output = subprocess.run(["openssl", "ciphers"], capture_output=True).stdout
111
+ output_str = output.decode("utf-8")
112
+ return output_str.strip().split("\n")[0]
113
+
114
+ @classmethod
115
+ def get_default_certificates(cls):
116
+ dvp = ssl.get_default_verify_paths()
117
+ if not os.path.exists(dvp.cafile):
118
+ raise TechnicalException(f"openssl CA file '{dvp.cafile}' doesn't exist. Example of resolution command (on Ubuntu): sudo ln -s /etc/ssl/certs/ca-certificates.crt {dvp.cafile}")
119
+ return (dvp.cafile, dvp.capath)
120
+
121
+ @classmethod
122
+ def get_certifi_ca_certs_file_path(cls):
123
+ import certifi
124
+ return certifi.where()
125
+
126
+ # @classmethod
127
+ # def get_localhost_certificate(cls):
128
+ # here = os.path.abspath(os.path.dirname(__file__))
129
+ # certfile_path = os.path.join(here, 'resources', 'certificates', 'localhost.crt')
130
+ # keyfile_path = os.path.join(here, 'resources', 'certificates', 'localhost.key')
131
+ # return (certfile_path, keyfile_path)
132
+
133
+ @classmethod
134
+ def get_tcpbin_certificates(cls):
77
135
  here = os.path.abspath(os.path.dirname(__file__))
78
- certfile_path = os.path.join(here, 'resources', 'certificates', 'localhost.crt')
79
- keyfile_path = os.path.join(here, 'resources', 'certificates', 'localhost.key')
80
- return (certfile_path, keyfile_path)
136
+ certfile_path = os.path.join(here, 'resources', 'certificates', 'tcpbin.crt')
137
+ keyfile_path = os.path.join(here, 'resources', 'certificates', 'tcpbin.key')
138
+ ca_certfile_path = os.path.join(here, 'resources', 'certificates', 'rootCACert.pem')
139
+
140
+ cls.ensure_tcpbin_certificates_are_valid(certfile_path, keyfile_path)
141
+
142
+ return (certfile_path, keyfile_path, ca_certfile_path)
143
+
144
+ @classmethod
145
+ def ensure_tcpbin_certificates_are_valid(cls, public_key_path, private_key_path, duration_seconds=600):
146
+ cls.__get_path_manager().makedirs(public_key_path)
147
+ cls.__get_path_manager().makedirs(private_key_path)
148
+
149
+ # Verify if certificate has expired
150
+ do_generate_certificates = True
151
+ if os.path.exists(public_key_path):
152
+ cmd = f"openssl x509 -checkend {duration_seconds} -noout -in '{public_key_path}'"
153
+ command = Command(cmd, do_log_output=True, do_raise_on_stderr=False, executable="/bin/bash")
154
+ command.start()
155
+ command.join()
156
+ if command.state is CommandStates.Success:
157
+ do_generate_certificates = False
158
+ elif command.return_code == 1:
159
+ do_generate_certificates = True
160
+ else:
161
+ raise TechnicalException(f"Error while executing openssl command [{cmd}]: error code={command.return_code} ; stdout: [{command.stdout}] ; stderr: [{command.stderr}]")
162
+
163
+ # Generate new certificates if needed
164
+ if do_generate_certificates:
165
+ cmd = f"curl -s https://tcpbin.com/api/client-cert"
166
+ command = Command(cmd, do_log_output=True, do_raise_on_stderr=False, executable="/bin/bash")
167
+ command.start()
168
+ command.join()
169
+ if command.state is not CommandStates.Success:
170
+ raise TechnicalException(f"Error while executing command [{cmd}] : [{command.stderr}]")
171
+
172
+ data = json.loads(command.stdout)
173
+ with open(public_key_path, 'wt') as fout:
174
+ fout.write(data['cert'])
175
+ with open(private_key_path, 'wt') as fout:
176
+ fout.write(data['key'])
177
+
178
+ @classmethod
179
+ def generate_new_self_signed_key_files(cls, public_key_path, private_key_path, openssl_args):
180
+ cls.__get_path_manager().makedirs(public_key_path)
181
+ cls.__get_path_manager().makedirs(private_key_path)
182
+
183
+ cmd = f"openssl req -out '{public_key_path}' -keyout '{private_key_path}' {openssl_args}"
184
+ command = Command(cmd, do_log_output=True, do_raise_on_stderr=False, executable="/bin/bash")
185
+ command.start()
186
+ command.join()
187
+
188
+ if command.state is not CommandStates.Success:
189
+ raise TechnicalException(f"Error while executing openssl command [{cmd}] : [{command.stderr}]")
190
+
191
+ @classmethod
192
+ def generate_new_self_signed_key_files_for_localhost(cls, public_key_path, private_key_path, algorithm='rsa:2048'):
193
+ SslManager.generate_new_self_signed_key_files(
194
+ public_key_path, private_key_path,
195
+ f"-x509 -newkey {algorithm} -noenc -sha256 -subj '/CN=localhost' -extensions EXT \
196
+ -config <( printf \"[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth\")"
197
+ )
81
198
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  from holado_test.scenario.step_tools import StepTools
17
17
  from holado.common.context.session_context import SessionContext
18
- from holado_test.behave.behave import *
18
+ from holado_test.behave.behave import * # @UnusedWildImport
19
19
  import logging
20
20
  from holado_core.common.tools.string_tools import StrTools
21
21