pypsrp 0.9.0rc1__tar.gz → 0.9.0rc2__tar.gz

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.
Files changed (120) hide show
  1. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/CHANGELOG.md +6 -0
  2. {pypsrp-0.9.0rc1/src/pypsrp.egg-info → pypsrp-0.9.0rc2}/PKG-INFO +4 -3
  3. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/README.md +2 -1
  4. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/lib.sh +0 -6
  5. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/win-setup.ps1 +185 -134
  6. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/pyproject.toml +2 -2
  7. pypsrp-0.9.0rc2/src/pypsrp/_pool_manager.py +142 -0
  8. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/negotiate.py +2 -2
  9. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/powershell.py +50 -0
  10. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/shell.py +38 -0
  11. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/wsman.py +45 -11
  12. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2/src/pypsrp.egg-info}/PKG-INFO +4 -3
  13. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/SOURCES.txt +5 -0
  14. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/requires.txt +1 -1
  15. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/conftest.py +11 -2
  16. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/_template.yml +1 -0
  17. pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_http_error.yml +12 -0
  18. pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_invalid_selectors.yml +14 -0
  19. pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_other_wsman_error.yml +14 -0
  20. pypsrp-0.9.0rc2/tests/tests_pypsrp/responses/test_psrp_is_alive_state_disconnected.yml +11 -0
  21. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_integration.py +18 -8
  22. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_powershell.py +67 -0
  23. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_wsman.py +7 -7
  24. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/LICENSE +0 -0
  25. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/MANIFEST.in +0 -0
  26. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/JEARole.psrc +0 -0
  27. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/JEARoleSettings.pssc +0 -0
  28. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/check-winrm.py +0 -0
  29. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/build_helpers/run-ci.sh +0 -0
  30. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/setup.cfg +0 -0
  31. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/__init__.py +0 -0
  32. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/_utils.py +0 -0
  33. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/client.py +0 -0
  34. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/complex_objects.py +0 -0
  35. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/encryption.py +0 -0
  36. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/exceptions.py +0 -0
  37. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/host.py +0 -0
  38. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/messages.py +0 -0
  39. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/pwsh_scripts/__init__.py +0 -0
  40. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/pwsh_scripts/copy.ps1 +0 -0
  41. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/pwsh_scripts/fetch.ps1 +0 -0
  42. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/py.typed +0 -0
  43. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp/serializer.py +0 -0
  44. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/dependency_links.txt +0 -0
  45. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/src/pypsrp.egg-info/top_level.txt +0 -0
  46. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/__init__.py +0 -0
  47. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/data/test_sanitise_clixml_with_error.xml +0 -0
  48. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/data/test_sanitise_clixml_with_no_errors.xml +0 -0
  49. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_expand_vars.yml +0 -0
  50. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file.yml +0 -0
  51. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_empty.yml +0 -0
  52. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_failure.yml +0 -0
  53. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_really_large.yml +0 -0
  54. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_copy_file_warning.yml +0 -0
  55. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_cmd.yml +0 -0
  56. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_cmd_environment.yml +0 -0
  57. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_ps.yml +0 -0
  58. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_ps_environment.yml +0 -0
  59. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_execute_ps_failure.yml +0 -0
  60. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file.yml +0 -0
  61. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_expand_vars.yml +0 -0
  62. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_fail_dir.yml +0 -0
  63. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_fail_missing.yml +0 -0
  64. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_client_fetch_file_hash_mismatch.yml +0 -0
  65. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_application_args.yml +0 -0
  66. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_clear_commands.yml +0 -0
  67. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_disconnect_runspaces.yml +0 -0
  68. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_disconnected_commands.yml +0 -0
  69. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_error_failed.yml +0 -0
  70. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_get_command_metadata.yml +0 -0
  71. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_key_exchange_timeout.yml +0 -0
  72. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_long_running_cmdlet.yml +0 -0
  73. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_merge_commands.yml +0 -0
  74. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_multiple_commands.yml +0 -0
  75. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_multiple_invocations.yml +0 -0
  76. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_nested_command.yml +0 -0
  77. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_no_profile.yml +0 -0
  78. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_open_runspace.yml +0 -0
  79. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_pshost_methods.yml +0 -0
  80. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_pshost_raw_ui_mocked_methods.yml +0 -0
  81. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_pshost_ui_mocked_methods.yml +0 -0
  82. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_receive_failure.yml +0 -0
  83. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_reset_runspace_state.yml +0 -0
  84. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_reset_runspace_state_fail.yml +0 -0
  85. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_run_protocol_version_2.1.yml +0 -0
  86. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_run_protocol_version_2.2.yml +0 -0
  87. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_run_protocol_version_2.3.yml +0 -0
  88. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_set_runspaces.yml +0 -0
  89. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_small_msg_size.yml +0 -0
  90. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_stream_no_output_invocation.yml +0 -0
  91. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_stream_output_invocation.yml +0 -0
  92. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_history.yml +0 -0
  93. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_input.yml +0 -0
  94. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_jea_configuration.yml +0 -0
  95. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_psrp_with_no_history.yml +0 -0
  96. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_bad_cmd_id.yml +0 -0
  97. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_environment.yml +0 -0
  98. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_extra_opts.yml +0 -0
  99. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_fail_poll_process.yml +0 -0
  100. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_no_cmd_shell.yml +0 -0
  101. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_noprofile.yml +0 -0
  102. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_open_already_opened.yml +0 -0
  103. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_operation_timeout.yml +0 -0
  104. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_send.yml +0 -0
  105. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_standard.yml +0 -0
  106. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_stderr_rc.yml +0 -0
  107. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_winrs_unicode.yml +0 -0
  108. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_wsman_update_envelope_size_150.yml +0 -0
  109. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_wsman_update_envelope_size_4096.yml +0 -0
  110. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/responses/test_wsman_update_envelope_size_500.yml +0 -0
  111. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_client.py +0 -0
  112. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_complex_objects.py +0 -0
  113. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_encryption.py +0 -0
  114. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_exceptions.py +0 -0
  115. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_host.py +0 -0
  116. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_messages.py +0 -0
  117. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_negotiate.py +0 -0
  118. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_serializer.py +0 -0
  119. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_shell.py +0 -0
  120. {pypsrp-0.9.0rc1 → pypsrp-0.9.0rc2}/tests/tests_pypsrp/test_utils.py +0 -0
@@ -5,10 +5,16 @@
5
5
  * Raised minimum Python version to 3.10
6
6
  * Added retry handler for WSMan `Receive` operations that can attempt to recover from a network disconnect during a command operation
7
7
  * Added `no_profile` option to `RunspacePool` to skip loading the user profile on the remote shell
8
+ * Added `idle_timeout` option to `RunspacePool` to control the idle timeout, in seconds, of the remote Runspace Pool
8
9
  * Made an `__enter__` and `__exit__` method for `PowerShell` to allow it to be used with a `with PowerShell(runspace) as ps:` syntax
9
10
  * On `__exit__` the pipeline will be closed using the `TERMINATE` signal to clean up any resources on the server end
10
11
  * `ps.close()` can also be called manually to clean up any resources and to prep the pipeline to be run again
11
12
  * Added `clear_streams()` onto a `PowerShell` object to clear the output and `streams` values so it is ready for a subsequent run
13
+ * Changed default service name used for Kerberos authentication from `WSMAN` to `host` to match the behaviour on Windows
14
+ * `WSMAN` was used for CredSSP authentication only on the native Windows client while `Negotiate/Kerberos` auth used `host`
15
+ * This should fix errors when authenticating with domain controllers which sometimes fail to register the `WSMAN` service for the host
16
+ * Added `is_alive(timeout=...)` on `RunspacePool` to check if the Runspace Pool is still alive and ready for use on the server
17
+ * Added `certificate_key_password` option to `WSMan` to support loaded client certificate keys that have been encrypted
12
18
 
13
19
  ## 0.8.1 - 2022-02-22
14
20
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypsrp
3
- Version: 0.9.0rc1
3
+ Version: 0.9.0rc2
4
4
  Summary: PowerShell Remoting Protocol and WinRM for Python
5
5
  Author-email: Jordan Borean <jborean93@gmail.com>
6
6
  License-Expression: MIT
@@ -18,7 +18,7 @@ Description-Content-Type: text/markdown
18
18
  License-File: LICENSE
19
19
  Requires-Dist: cryptography>=3.1
20
20
  Requires-Dist: pyspnego<1.0.0,>=0.7.0
21
- Requires-Dist: requests>=2.9.1
21
+ Requires-Dist: requests>=2.27.0
22
22
  Provides-Extra: credssp
23
23
  Requires-Dist: requests-credssp>=2.0.0; extra == "credssp"
24
24
  Provides-Extra: kerberos
@@ -224,7 +224,8 @@ These are the options that can be used to setup `WSMan`;
224
224
  * `data_locale`: The `wsmv:DataLocale` value to set on each WSMan request. This specifies the format in which numerical data is presented in the response text, default is the value of `locale`
225
225
  * `reconnection_retries`: Number of retries on a connection problem, default is `0`
226
226
  * `reconnection_backoff`: Number of seconds to backoff in between reconnection attempts (first sleeps X, then sleeps 2*X, 4*X, 8*X, ...), default is `2.0`
227
- * `certificate_key_pem`: The path to the certificate key used in `certificate` authentication
227
+ * `certificate_key_pem`: The path to the certificate key used in `certificate` authentication. The key can be in either a `PKCS#1` or `PKCS#8` format
228
+ * `certificate_key_password`: The password for `certificate_key_pem` if it is encrypted
228
229
  * `certificate_pem`: The path to the certificate used in `certificate` authentication
229
230
  * `credssp_auth_mechanism`: The sub-auth mechanism used in CredSSP, default is `auto`, choices are `auto`, `ntlm`, or `kerberos`
230
231
  * `credssp_disable_tlsv1_2`: Whether to used CredSSP auth over the insecure TLSv1.0, default is `False`
@@ -182,7 +182,8 @@ These are the options that can be used to setup `WSMan`;
182
182
  * `data_locale`: The `wsmv:DataLocale` value to set on each WSMan request. This specifies the format in which numerical data is presented in the response text, default is the value of `locale`
183
183
  * `reconnection_retries`: Number of retries on a connection problem, default is `0`
184
184
  * `reconnection_backoff`: Number of seconds to backoff in between reconnection attempts (first sleeps X, then sleeps 2*X, 4*X, 8*X, ...), default is `2.0`
185
- * `certificate_key_pem`: The path to the certificate key used in `certificate` authentication
185
+ * `certificate_key_pem`: The path to the certificate key used in `certificate` authentication. The key can be in either a `PKCS#1` or `PKCS#8` format
186
+ * `certificate_key_password`: The password for `certificate_key_pem` if it is encrypted
186
187
  * `certificate_pem`: The path to the certificate used in `certificate` authentication
187
188
  * `credssp_auth_mechanism`: The sub-auth mechanism used in CredSSP, default is `auto`, choices are `auto`, `ntlm`, or `kerberos`
188
189
  * `credssp_disable_tlsv1_2`: Whether to used CredSSP auth over the insecure TLSv1.0, default is `False`
@@ -28,12 +28,6 @@ lib::setup::windows_requirements() {
28
28
  -Password "${PYPSRP_PASSWORD}" \
29
29
  -CertPath "${PYPSRP_CERT_DIR}" \
30
30
  -InformationAction Continue
31
-
32
- # FIXME: For some reason cert auth is failing with. Need to figure out what's happening here and unset this
33
- # pypsrp.exceptions.WSManFaultError: Received a WSManFault message. (Code: 2150859262, Machine: localhost,
34
- # Reason: The WS-Management service cannot process the operation. An attempt to query mapped credential failed.
35
- # This will happen if the security context associated with WinRM service has changed since the credential was originally mapped
36
- unset PYPSRP_CERT_DIR
37
31
  }
38
32
 
39
33
  lib::setup::system_requirements() {
@@ -1,3 +1,9 @@
1
+ using namespace System.IO
2
+ using namespace System.Management.Automation
3
+ using namespace System.Runtime.InteropServices
4
+ using namespace System.Security.Cryptography
5
+ using namespace System.Security.Cryptography.X509Certificates
6
+
1
7
  [CmdletBinding()]
2
8
  param(
3
9
  [Parameter(Mandatory)]
@@ -17,83 +23,6 @@ $ErrorActionPreference = "Stop"
17
23
 
18
24
  Write-Information -MessageData "Configuring WinRM for pypsrp tests for $UserName"
19
25
 
20
- Function New-LegacySelfSignedCert {
21
- [CmdletBinding()]
22
- param (
23
- [Parameter(Mandatory)]
24
- [String]
25
- $Subject,
26
-
27
- [Parameter(Mandatory)]
28
- [Int32]
29
- $ValidDays
30
- )
31
-
32
- Write-Information -MessageData "Creating self-signed certificate of CN=$Subject for $ValidDays days"
33
- $subject_name = New-Object -ComObject X509Enrollment.CX500DistinguishedName
34
- $subject_name.Encode("CN=$Subject", 0)
35
-
36
- $private_key = New-Object -ComObject X509Enrollment.CX509PrivateKey
37
- $private_key.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
38
- $private_key.KeySpec = 1
39
- $private_key.Length = 4096
40
- $private_key.SecurityDescriptor = "D:PAI(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)(A;;0x80120089;;;NS)"
41
- $private_key.MachineContext = 1
42
- $private_key.Create()
43
-
44
- $server_auth_oid = New-Object -ComObject X509Enrollment.CObjectId
45
- $server_auth_oid.InitializeFromValue("1.3.6.1.5.5.7.3.1")
46
-
47
- $ekuoids = New-Object -ComObject X509Enrollment.CObjectIds
48
- $ekuoids.Add($server_auth_oid)
49
-
50
- $eku_extension = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage
51
- $eku_extension.InitializeEncode($ekuoids)
52
-
53
- $name = @($env:COMPUTERNAME, ([System.Net.Dns]::GetHostByName($env:COMPUTERNAME).Hostname))
54
- $alt_names = New-Object -ComObject X509Enrollment.CAlternativeNames
55
- foreach ($name in $name) {
56
- $alt_name = New-Object -ComObject X509Enrollment.CAlternativeName
57
- $alt_name.InitializeFromString(0x3, $name)
58
- $alt_names.Add($alt_name)
59
- }
60
- $alt_names_extension = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames
61
- $alt_names_extension.InitializeEncode($alt_names)
62
-
63
- $digital_signature = [Security.Cryptography.X509Certificates.X509KeyUsageFlags]::DigitalSignature
64
- $key_encipherment = [Security.Cryptography.X509Certificates.X509KeyUsageFlags]::KeyEncipherment
65
- $key_usage = [int]($digital_signature -bor $key_encipherment)
66
- $key_usage_extension = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage
67
- $key_usage_extension.InitializeEncode($key_usage)
68
- $key_usage_extension.Critical = $true
69
-
70
- $signature_oid = New-Object -ComObject X509Enrollment.CObjectId
71
- $sha256_oid = New-Object -TypeName Security.Cryptography.Oid -ArgumentList "SHA256"
72
- $signature_oid.InitializeFromValue($sha256_oid.Value)
73
-
74
- $certificate = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate
75
- $certificate.InitializeFromPrivateKey(2, $private_key, "")
76
- $certificate.Subject = $subject_name
77
- $certificate.Issuer = $certificate.Subject
78
- $certificate.NotBefore = (Get-Date).AddDays(-1)
79
- $certificate.NotAfter = $certificate.NotBefore.AddDays($ValidDays)
80
- $certificate.X509Extensions.Add($key_usage_extension)
81
- $certificate.X509Extensions.Add($alt_names_extension)
82
- $certificate.X509Extensions.Add($eku_extension)
83
- $certificate.SignatureInformation.HashAlgorithm = $signature_oid
84
- $certificate.Encode()
85
-
86
- $enrollment = New-Object -ComObject X509Enrollment.CX509Enrollment
87
- $enrollment.InitializeFromRequest($certificate)
88
- $certificate_data = $enrollment.CreateRequest(0)
89
- $enrollment.InstallResponse(2, $certificate_data, 0, "")
90
-
91
- $parsed_certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
92
- $parsed_certificate.Import([System.Text.Encoding]::UTF8.GetBytes($certificate_data))
93
-
94
- return $parsed_certificate
95
- }
96
-
97
26
  function New-WinRMFirewallRule {
98
27
  [CmdletBinding()]
99
28
  param (
@@ -148,7 +77,7 @@ function New-WinRMFirewallRule {
148
77
  try {
149
78
  $fw.Rules.Add($rule)
150
79
  }
151
- catch [System.Runtime.InteropServices.COMException] {
80
+ catch [COMException] {
152
81
  # E_UNEXPECTED 0x80000FFFF means the rule already exists
153
82
  if ($_.Exception.ErrorCode -eq 0x8000FFFF) {
154
83
  Write-Information -MessageData "WinRM $Protocol firewall rule already exists, deleting before recreating"
@@ -166,7 +95,15 @@ function New-WinRMFirewallRule {
166
95
 
167
96
  function Reset-WinRMConfig {
168
97
  [CmdletBinding()]
169
- Param()
98
+ Param(
99
+ [Parameter(Mandatory)]
100
+ [X509Certificate2]
101
+ $CACertificate,
102
+
103
+ [Parameter(Mandatory)]
104
+ [String]
105
+ $CtlStore
106
+ )
170
107
 
171
108
  Write-Verbose "Removing all existing WinRM listeners"
172
109
  Get-ChildItem -LiteralPath WSMan:\localhost\Listener | Remove-Item -Force -Recurse
@@ -181,7 +118,15 @@ function Reset-WinRMConfig {
181
118
  }
182
119
  New-WSManInstance -ResourceURI winrm/config/listener -SelectorSet $selectorSet -ValueSet $valueSet > $null
183
120
 
184
- $certificate = New-LegacySelfSignedCert -Subject $env:COMPUTERNAME -ValidDays 1095
121
+ $certParams = @{
122
+ CertStoreLocation = 'Cert:\LocalMachine\My'
123
+ DnsName = $env:COMPUTERNAME, 'localhost'
124
+ NotAfter = (Get-Date).AddYears(1)
125
+ Provider = 'Microsoft Software Key Storage Provider'
126
+ Signer = $CACertificate
127
+ Subject = "CN=$env:COMPUTERNAME"
128
+ }
129
+ $certificate = New-SelfSignedCertificate @certParams
185
130
  $selectorSet = @{
186
131
  Transport = "HTTPS"
187
132
  Address = "*"
@@ -223,37 +168,64 @@ function Reset-WinRMConfig {
223
168
  PropertyType = "DWord"
224
169
  Force = $true
225
170
  }
226
- New-ItemProperty @regInfo
171
+ New-ItemProperty @regInfo > $null
172
+
173
+ Write-Information -MessageData "Configuring WinRM HTTPS binding to use CTL trust store '$CtlStore'"
174
+ $existingBinding = netsh.exe http show sslcert ipport=0.0.0.0:5986 json=enable
175
+ if ($LASTEXITCODE) {
176
+ throw "Failed to get existing WinRM HTTPS binding:`n$existingBinding"
177
+ }
178
+ $binding = $existingBinding | ConvertFrom-Json | Select-Object -ExpandProperty SslCertificateBindings
179
+
180
+ $out = netsh.exe @(
181
+ "http"
182
+ "update"
183
+ "sslcert"
184
+ "ipport=0.0.0.0:5986"
185
+ "appid=$($binding.GuidString)"
186
+ "certhash=$($certificate.Thumbprint)"
187
+ "sslctlstorename=$CtlStore"
188
+ ) 2>&1
189
+ if ($LASTEXITCODE) {
190
+ throw "Failed to set sslctlstorename for WinRM binding:`n$out"
191
+ }
227
192
 
228
193
  Write-Information -MessageData "WinRM and PS Remoting have been set up successfully"
229
194
  }
230
195
 
231
196
  Function New-CertificateAuthBinding {
197
+ [OutputType([string])]
232
198
  [CmdletBinding()]
233
199
  Param (
234
200
  [String]
235
201
  $Name,
236
202
 
237
203
  [String]
238
- $CertPath
204
+ $CertPath,
205
+
206
+ [X509Certificate2]
207
+ $CACertificate,
208
+
209
+ [PSCredential]
210
+ $Credential
239
211
  )
240
212
 
241
213
  Write-Information -MessageData "Generating self signed certificate for authentication of user $Name"
242
214
  $certInfo = @{
215
+ CertStoreLocation = "Cert:\CurrentUser\My"
216
+ Provider = 'Microsoft Software Key Storage Provider'
217
+ Signer = $CACertificate
243
218
  Subject = "CN=$Name"
244
- KeyUsage = "DigitalSignature", "KeyEncipherment"
245
- KeyAlgorithm = "RSA"
246
- KeyLength = 2048
247
219
  TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.2", "2.5.29.17={text}upn=$Name@localhost")
248
220
  Type = "Custom"
249
- CertStoreLocation = "Cert:\CurrentUser\My"
250
221
  }
251
222
  $cert = New-SelfSignedCertificate @certInfo
252
223
 
253
224
  Write-Information -MessageData "Exporting private key in a PFX file"
254
- [System.IO.File]::WriteAllBytes("$CertPath\cert.pfx", $cert.Export("Pfx"))
225
+ [File]::WriteAllBytes("$CertPath\cert.pfx", $cert.Export("Pfx"))
255
226
 
256
227
  Write-Information -MessageData "Converting private key to PEM format with openssl"
228
+ $certPassword = $Credential.GetNetworkCredential().Password
257
229
  $out = openssl.exe @(
258
230
  "pkcs12",
259
231
  "-in", "$CertPath\cert.pfx",
@@ -266,35 +238,46 @@ Function New-CertificateAuthBinding {
266
238
  if ($LASTEXITCODE) {
267
239
  throw "Failed to extract key from PEM:`n$out"
268
240
  }
241
+ $out = openssl.exe @(
242
+ "pkcs12",
243
+ "-in", "$CertPath\cert.pfx",
244
+ "-nocerts",
245
+ "-out", "$CertPath\cert_enc_key.pem",
246
+ "-passin", "pass:",
247
+ "-passout", "pass:$certPassword"
248
+ ) 2>&1
249
+ if ($LASTEXITCODE) {
250
+ throw "Failed to extract encrypted key from PEM:`n$out"
251
+ }
269
252
  Remove-Item -Path "$CertPath\cert.pfx" -Force
270
253
 
271
- # WinRM seems to be very picky about the type of cert in the trusted root and people store. Make sure this is set
254
+ # WinRM seems to be very picky about the type of cert in the trusted people store, make sure this is set
272
255
  # to the cert and not cert + key.
273
- $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($cert.RawData)
256
+ $certNoKey = [X509Certificate2]::new($cert.RawData)
274
257
 
275
- Write-Information -MessageData "Exporting cert and key of user certificate"
276
- $key_pem = Get-Content -Path "$CertPath\cert_key.pem"
277
- Remove-Item -Path "$CertPath\cert_key.pem" -Force
278
- [System.IO.File]::WriteAllLines("$CertPath\cert.pem", @(
279
- $key_pem
258
+ Write-Information -MessageData "Exporting user certificate PEM"
259
+ [File]::WriteAllLines("$CertPath\cert.pem", @(
280
260
  "-----BEGIN CERTIFICATE-----"
281
- [System.Convert]::ToBase64String($cert.RawData) -replace ".{64}", "$&`n"
261
+ [Convert]::ToBase64String($certNoKey.RawData) -replace ".{64}", "$&`n"
282
262
  "-----END CERTIFICATE-----"
283
263
  ))
284
264
 
285
- Write-Information -MessageData "Importing cert into LocalMachine\Root"
286
- $store = Get-Item -Path Cert:\LocalMachine\Root
287
- $store.Open("MaxAllowed")
288
- $store.Add($cert)
289
- $store.Close()
290
-
291
265
  Write-Information -MessageData "Importing cert into LocalMachine\TrustedPeople"
292
266
  $store = Get-Item -Path Cert:\LocalMachine\TrustedPeople
293
- $store.Open("MaxAllowed")
294
- $store.Add($cert)
295
- $store.Close()
267
+ $store.Open([OpenFlags]::ReadWrite)
268
+ $store.Add($certNoKey)
269
+ $store.Dispose()
270
+
271
+ $credBinding = @{
272
+ Credential = $Credential
273
+ Force = $true
274
+ Issuer = $CACertificate.Thumbprint
275
+ Path = "WSMan:\localhost\ClientCertificate"
276
+ Subject = "$Name@localhost"
277
+ }
278
+ New-Item @credBinding > $null
296
279
 
297
- $cert
280
+ $cert.Thumbprint
298
281
  }
299
282
 
300
283
  Function New-JEAConfiguration {
@@ -310,12 +293,12 @@ Function New-JEAConfiguration {
310
293
  $modulePath = Join-Path -Path $env:ProgramFiles -ChildPath "WindowsPowerShell\Modules\$Name"
311
294
  Write-Information -MessageData "Setting up JEA PowerShell module path at '$modulePath'"
312
295
  if (-not (Test-Path -Path $modulePath)) {
313
- New-Item -Path $modulePath -ItemType Directory
296
+ New-Item -Path $modulePath -ItemType Directory | Out-Null
314
297
  }
315
298
 
316
299
  $functionsPath = Join-Path -Path $modulePath -ChildPath "$($Name)Functions.psm1"
317
300
  if (-not (Test-Path -Path $functionsPath)) {
318
- New-Item -Path $functionsPath -ItemType File
301
+ New-Item -Path $functionsPath -ItemType File | Out-Null
319
302
  }
320
303
 
321
304
  $manifestPath = Join-Path -Path $modulePath -ChildPath "$($Name).psd1"
@@ -325,7 +308,7 @@ Function New-JEAConfiguration {
325
308
 
326
309
  $rolePath = Join-Path -Path $modulePath -ChildPath "RoleCapabilities"
327
310
  if (-not (Test-Path -Path $rolePath)) {
328
- New-Item -Path $rolePath -ItemType Directory
311
+ New-Item -Path $rolePath -ItemType Directory | Out-Null
329
312
  }
330
313
 
331
314
  $jeaRoleSrc = Join-Path -Path $JEAConfigPath -ChildPath "$($Name).psrc"
@@ -339,43 +322,111 @@ Function New-JEAConfiguration {
339
322
  }
340
323
  }
341
324
 
342
- $secPassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
343
- $userCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $env:COMPUTERNAME\$UserName, $secPassword
344
- $userCertCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName, $secPassword
325
+ $caParams = @{
326
+ Extension = @(
327
+ [X509BasicConstraintsExtension]::new($true, $false, 0, $true)
328
+ [X509KeyUsageExtension]::new('KeyCertSign', $true)
329
+ )
330
+ CertStoreLocation = 'Cert:\CurrentUser\My'
331
+ NotAfter = (Get-Date).AddYears(1)
332
+ Provider = 'Microsoft Software Key Storage Provider'
333
+ Subject = 'CN=PyPSRP CA'
334
+ Type = 'Custom'
335
+ }
336
+ Write-Information -MessageData "Creating CA certificate"
337
+ $ca = New-SelfSignedCertificate @caParams
338
+
339
+ $root = Get-Item -LiteralPath Cert:\LocalMachine\Root
340
+ $root.Open([OpenFlags]::ReadWrite)
341
+ $root.Add($ca)
342
+ $root.Dispose()
343
+
344
+ # Setup a specific store for the WinRM CTL as GHA's root store is too large
345
+ # and causes issues with WinRM cert selection during authentication
346
+ $ctlStoreName = 'WinRMTrustedIssuers'
347
+ $ctlStore = [X509Store]::new(
348
+ $ctlStoreName,
349
+ [StoreLocation]::LocalMachine)
350
+ $ctlStore.Open([OpenFlags]::ReadWrite)
351
+ $ctlStore.Add([X509Certificate2]::new($ca.RawData)) # Strip key affinity
352
+ $ctlStore.Dispose()
345
353
 
346
- Enable-PSRemoting -Force
347
- Start-Service -Name WinRM
348
- Reset-WinRMConfig
354
+ $secPassword = ConvertTo-SecureString -String $Password -AsPlainText -Force
355
+ $userCredential = [PSCredential]::new("$env:COMPUTERNAME\$UserName", $secPassword)
349
356
 
350
357
  $localUser = New-LocalUser -Name $UserName -Password $secPassword -AccountNeverExpires -PasswordNeverExpires
351
358
  Add-LocalGroupMember -Group Administrators -Member $localUser
352
359
 
353
- $clientCertificate = New-CertificateAuthBinding -Name $UserName -CertPath $CertPath
354
- $certChain = [Security.Cryptography.X509Certificates.X509Chain]::new()
355
- [void]$certChain.Build($clientCertificate)
356
-
357
- $credBinding = @{
358
- Path = "WSMan:\localhost\ClientCertificate"
359
- Subject = "$UserName@localhost"
360
- URI = "*"
361
- Issuer = $certChain.ChainElements.Certificate[-1].Thumbprint
362
- Credential = $userCertCredential
363
- Force = $true
360
+ Enable-PSRemoting -Force
361
+ Start-Service -Name WinRM
362
+ Reset-WinRMConfig -CACertificate $ca -CtlStore $ctlStoreName
363
+ Write-Information -MessageData "Setting up JEA configuration"
364
+ New-JEAConfiguration -Name JEARole -JEAConfigPath $PSScriptRoot
365
+ Register-PSSessionConfiguration -Path "$PSScriptRoot\JEARoleSettings.pssc" -Name JEARole -Force > $null
366
+ Restart-Service -Name winrm
367
+
368
+ # It is important we setup the certificate auth binding after the JEA session is
369
+ # registered. JEA will change the WinRM service account from NetworkService to
370
+ # SYSTEM and cert auth bindings are encrypted based on the service account.
371
+ $clientCertParams = @{
372
+ Name = $UserName
373
+ CertPath = $CertPath
374
+ CACertificate = $ca
375
+ Credential = $userCredential
364
376
  }
365
- New-Item @credBinding
377
+ $clientCertificate = New-CertificateAuthBinding @clientCertParams
366
378
 
367
- New-JEAConfiguration -Name JEARole -JEAConfigPath $PSScriptRoot
368
- Register-PSSessionConfiguration -Path "$PSScriptRoot\JEARoleSettings.pssc" -Name JEARole -Force
379
+ # Only remove the CA/Key from CurrentUser\My after all other certs have been generated.
380
+ Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($ca.Thumbprint)" -Force
369
381
 
382
+ # Do one last restart, I've found that sometimes the service gets into a funky
383
+ # state after all the changes above.
370
384
  Restart-Service -Name winrm
371
385
 
372
- Write-Information -MessageData "Testing WinRM connection"
386
+ Write-Information -MessageData "Testing WinRM connection over HTTP"
373
387
  $invokeParams = @{
374
388
  ComputerName = 'localhost'
375
- ScriptBlock = { whoami.exe }
376
- SessionOption = (New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck)
389
+ ScriptBlock = { [Environment]::UserName }
390
+ }
391
+ $user = Invoke-Command @invokeParams -Credential $userCredential
392
+ if ($user -ne $UserName) {
393
+ throw "WinRM authentication did not return expected user. Expected: $UserName, Actual: $user"
394
+ }
395
+
396
+ # Seems like the HTTPS service can get into a bit of a funk based on our setup
397
+ # We retry a few times to get a successful connection rather than fail immediately.
398
+ Write-Information -MessageData "Testing WinRM connection over HTTPS"
399
+ $attempt = 0
400
+ while ($true) {
401
+ try {
402
+ $user = Invoke-Command @invokeParams -UseSSL -Credential $userCredential
403
+ break
404
+ }
405
+ catch {
406
+ if ($attempt -gt 4) {
407
+ throw
408
+ }
409
+
410
+ Write-Information -MessageData "WinRM over HTTPS connection failed - $_`nRetrying in 5 seconds..."
411
+ $attempt++
412
+
413
+ Start-Sleep -Seconds 5
414
+ }
415
+ }
416
+ if ($user -ne $UserName) {
417
+ throw "WinRM authentication over HTTPS did not return expected user. Expected: $UserName, Actual: $user"
418
+ }
419
+
420
+ Write-Information -MessageData "Testing WinRM connection over HTTPS with certificate authentication"
421
+ $user = Invoke-Command @invokeParams -UseSSL -CertificateThumbprint $clientCertificate
422
+ if ($user -ne $UserName) {
423
+ throw "Certificate authentication did not return expected user. Expected: $UserName, Actual: $user"
377
424
  }
378
- Invoke-Command @invokeParams -Credential $userCredential
379
425
 
380
- # Write-Information -MessageData "Testing WinRM connection with certificates"
381
- # Invoke-Command @invokeParams -CertificateThumbprint $thumbprint
426
+ Write-Information -MessageData "Testing WinRM connection with JEA"
427
+ $value = Invoke-Command -ComputerName localhost -ScriptBlock {
428
+ Get-Item -Path WSMan:\localhost\Service\AllowUnencrypted
429
+ } -ConfigurationName JEARole -Credential $userCredential
430
+ if (-not $value.Value) {
431
+ throw "JEA WinRM session did not return expected AllowUnencrypted value of True. Actual: $($value.Value)"
432
+ }
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pypsrp"
9
- version = "0.9.0rc1"
9
+ version = "0.9.0rc2"
10
10
  description = "PowerShell Remoting Protocol and WinRM for Python"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -28,7 +28,7 @@ classifiers = [
28
28
  dependencies = [
29
29
  "cryptography >= 3.1", # default_backend made an optional kwarg
30
30
  "pyspnego >= 0.7.0, < 1.0.0",
31
- "requests >= 2.9.1",
31
+ "requests >= 2.27.0", # requests official support for 3.10+
32
32
  ]
33
33
 
34
34
  [project.urls]
@@ -0,0 +1,142 @@
1
+ # Copyright: (c) 2026, Jordan Borean (@jborean93) <jborean93@gmail.com>
2
+ # MIT License (see LICENSE or https://opensource.org/licenses/MIT)
3
+
4
+ from __future__ import annotations
5
+
6
+ import contextvars
7
+ import functools
8
+ import types
9
+ import typing as t
10
+
11
+ import requests.adapters
12
+ from requests.packages.urllib3.util.retry import Retry
13
+
14
+ T = t.TypeVar("T")
15
+
16
+
17
+ class NewConnectionDisabled(Exception):
18
+ """Raised when new connections are being made but have been disabled in the current context."""
19
+
20
+
21
+ class DisableNewConnectionsContext:
22
+ """Context manager to disable new connections from being created."""
23
+
24
+ __slots__ = "_contextvar_token"
25
+
26
+ _contextvar: t.ClassVar[contextvars.ContextVar] = contextvars.ContextVar("NewConnectionContext")
27
+ _contextvar_token: contextvars.Token
28
+
29
+ @classmethod
30
+ def current(cls) -> DisableNewConnectionsContext | None:
31
+ """Returns the current context or None if not set."""
32
+ try:
33
+ return cls._contextvar.get()
34
+ except LookupError:
35
+ return None
36
+
37
+ def __enter__(self) -> DisableNewConnectionsContext:
38
+ self._contextvar_token = self.__class__._contextvar.set(self)
39
+ return self
40
+
41
+ def __exit__(
42
+ self,
43
+ exc_type: type[BaseException] | None,
44
+ exc_val: BaseException | None,
45
+ exc_tb: types.TracebackType | None,
46
+ ) -> bool | None:
47
+ self.__class__._contextvar.reset(self._contextvar_token)
48
+ del self._contextvar_token
49
+
50
+ return None
51
+
52
+
53
+ class HTTPSAdapterWithKeyPassword(requests.adapters.HTTPAdapter):
54
+
55
+ def __init__(
56
+ self,
57
+ *args: t.Any,
58
+ _pypsrp_key_password: str | None = None,
59
+ **kwargs: t.Any,
60
+ ) -> None:
61
+ self.__key_password = _pypsrp_key_password
62
+ super().__init__(*args, **kwargs)
63
+
64
+ def init_poolmanager(
65
+ self,
66
+ connections,
67
+ maxsize,
68
+ block=False,
69
+ **pool_kwargs,
70
+ ):
71
+ return super().init_poolmanager(
72
+ connections,
73
+ maxsize,
74
+ block,
75
+ key_password=self.__key_password,
76
+ **pool_kwargs,
77
+ )
78
+
79
+
80
+ def _wrap(
81
+ func: t.Callable[..., T],
82
+ *,
83
+ before: t.Callable[[], None] | None = None,
84
+ after: t.Callable[[T], None] | None = None,
85
+ ) -> t.Callable[..., T]:
86
+ """Wraps a function with before and after callables."""
87
+
88
+ @functools.wraps(func)
89
+ def wrapper(*args: t.Any, **kwargs: t.Any) -> T:
90
+ if before:
91
+ before()
92
+
93
+ res = func(*args, **kwargs)
94
+
95
+ if after:
96
+ after(res)
97
+
98
+ return res
99
+
100
+ return wrapper
101
+
102
+
103
+ def _raise_if_connections_disabled():
104
+ if DisableNewConnectionsContext.current() is not None:
105
+ raise NewConnectionDisabled()
106
+
107
+
108
+ def _create_new_connection_with_failure(connection):
109
+ connection.connect = _wrap(connection.connect, before=_raise_if_connections_disabled)
110
+
111
+
112
+ def _create_connection_pool(pool):
113
+ # This is called in the urllib3 pool when a new connection is needed. We
114
+ # wrap it so we can wrap the connect method when a new connection is
115
+ # created.
116
+ pool.ConnectionCls = _wrap(pool.ConnectionCls, after=_create_new_connection_with_failure)
117
+
118
+
119
+ def create_request_adapter( # type: ignore[no-any-unimported] # requests does not have typing stubs for urllib3
120
+ *,
121
+ max_retries: Retry,
122
+ key_password: str | None = None,
123
+ ) -> requests.adapters.HTTPAdapter:
124
+ """Creates a HTTPAdapter with support for disabling new connections via context."""
125
+ adapter: requests.adapters.HTTPAdapter
126
+ if key_password is not None:
127
+ adapter = HTTPSAdapterWithKeyPassword(
128
+ max_retries=max_retries,
129
+ _pypsrp_key_password=key_password,
130
+ )
131
+ else:
132
+ adapter = requests.adapters.HTTPAdapter(max_retries=max_retries)
133
+
134
+ # pool_classes_by_scheme stores the urllib3 pool types used when creating
135
+ # the connection pool for http/https. We wrap the __init__ method so we can
136
+ # inject our custom ConnectionCls that wraps the connect method when
137
+ # created.
138
+ pool_classes = adapter.poolmanager.pool_classes_by_scheme
139
+ for scheme, pool_cls in list(pool_classes.items()):
140
+ pool_classes[scheme] = _wrap(pool_cls, after=_create_connection_pool)
141
+
142
+ return adapter