besapi 3.8.3__tar.gz → 3.9.1__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 (25) hide show
  1. {besapi-3.8.3/src/besapi.egg-info → besapi-3.9.1}/PKG-INFO +1 -1
  2. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/besapi.py +3 -1
  3. besapi-3.9.1/src/besapi/plugin_utilities_win.py +167 -0
  4. {besapi-3.8.3 → besapi-3.9.1/src/besapi.egg-info}/PKG-INFO +1 -1
  5. {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/SOURCES.txt +1 -0
  6. {besapi-3.8.3 → besapi-3.9.1}/tests/test_besapi.py +84 -1
  7. {besapi-3.8.3 → besapi-3.9.1}/LICENSE.txt +0 -0
  8. {besapi-3.8.3 → besapi-3.9.1}/MANIFEST.in +0 -0
  9. {besapi-3.8.3 → besapi-3.9.1}/README.md +0 -0
  10. {besapi-3.8.3 → besapi-3.9.1}/pyproject.toml +0 -0
  11. {besapi-3.8.3 → besapi-3.9.1}/setup.cfg +0 -0
  12. {besapi-3.8.3 → besapi-3.9.1}/setup.py +0 -0
  13. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/__init__.py +0 -0
  14. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/__main__.py +0 -0
  15. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/plugin_utilities.py +0 -0
  16. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/schemas/BES.xsd +0 -0
  17. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/schemas/BESAPI.xsd +0 -0
  18. {besapi-3.8.3 → besapi-3.9.1}/src/besapi/schemas/BESActionSettings.xsd +0 -0
  19. {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/dependency_links.txt +0 -0
  20. {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/requires.txt +0 -0
  21. {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/top_level.txt +0 -0
  22. {besapi-3.8.3 → besapi-3.9.1}/src/bescli/__init__.py +0 -0
  23. {besapi-3.8.3 → besapi-3.9.1}/src/bescli/__main__.py +0 -0
  24. {besapi-3.8.3 → besapi-3.9.1}/src/bescli/bescli.py +0 -0
  25. {besapi-3.8.3 → besapi-3.9.1}/tests/tests.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: besapi
3
- Version: 3.8.3
3
+ Version: 3.9.1
4
4
  Summary: Library for working with the BigFix REST API
5
5
  Home-page: https://github.com/jgstew/besapi
6
6
  Author: Matt Hansen, James Stewart
@@ -25,7 +25,7 @@ import lxml.etree
25
25
  import lxml.objectify
26
26
  import requests
27
27
 
28
- __version__ = "3.8.3"
28
+ __version__ = "3.9.1"
29
29
 
30
30
  besapi_logger = logging.getLogger("besapi")
31
31
 
@@ -479,6 +479,8 @@ class BESConnection:
479
479
 
480
480
  This will submit the relevance string as json instead of html form data.
481
481
  """
482
+ # not sure if the following is needed to handle some cases:
483
+ # relevance = "(it as string) of ( " + relevance + " )"
482
484
  rel_result_array = self.session_relevance_json_array(relevance, **kwargs)
483
485
  # Ensure each element is converted to a string
484
486
  return "\n".join(map(str, rel_result_array))
@@ -0,0 +1,167 @@
1
+ """
2
+ Plugin utilities for Windows systems, including DPAPI encryption/decryption
3
+ and Windows Registry access.
4
+ """
5
+
6
+ import base64
7
+ import logging
8
+ import sys
9
+ from typing import Union
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ try:
14
+ import winreg
15
+ except (ImportError, ModuleNotFoundError) as e:
16
+ if not sys.platform.startswith("win"):
17
+ raise RuntimeError(
18
+ "This script requires the 'winreg' module, which is only available on Windows."
19
+ ) from e
20
+ else:
21
+ raise e
22
+
23
+ try:
24
+ import win32crypt
25
+ except (ImportError, ModuleNotFoundError) as e:
26
+ if not sys.platform.startswith("win"):
27
+ raise RuntimeError("This script only works on Windows systems") from e
28
+ raise ImportError(
29
+ "This script requires the pywin32 package. Install it via 'pip install pywin32'."
30
+ ) from e
31
+
32
+
33
+ def win_dpapi_encrypt_str(
34
+ plaintext: str, scope_flags: int = 4, entropy: Union[str, bytes, None] = None
35
+ ) -> Union[str, None]:
36
+ """Encrypt a string using Windows DPAPI and return it as a base64-encoded string.
37
+
38
+ Args:
39
+ plaintext (str): The string to encrypt.
40
+ scope_flags (int): The context scope for encryption (default is 4).
41
+ entropy (bytes | None): Optional entropy for encryption.
42
+
43
+ Returns:
44
+ str: The base64-encoded encrypted string.
45
+ """
46
+ if not plaintext or plaintext.strip() == "":
47
+ logger.warning("No plaintext provided for encryption.")
48
+ return None
49
+
50
+ # 1. Convert the plaintext string to bytes
51
+ plaintext_bytes = plaintext.encode("utf-8")
52
+
53
+ # 2. Call CryptProtectData.
54
+ # The last parameter (flags) is set to 4
55
+ # to indicate that the data should be encrypted in the machine context.
56
+ #
57
+ # The function returns a tuple: (description, encrypted_bytes)
58
+ # We only need the second element.
59
+ encrypted_bytes = win32crypt.CryptProtectData(
60
+ plaintext_bytes,
61
+ None, # Description
62
+ entropy, # Optional entropy
63
+ None, # Reserved
64
+ None, # Prompt Struct
65
+ scope_flags,
66
+ )
67
+
68
+ # 3. Encode the encrypted bytes to a Base64 string
69
+ encrypted_b64 = base64.b64encode(encrypted_bytes).decode("utf-8")
70
+
71
+ return encrypted_b64
72
+
73
+
74
+ def win_dpapi_decrypt_base64(
75
+ encrypted_b64: str, scope_flags: int = 4, entropy: Union[str, bytes, None] = None
76
+ ) -> Union[str, None]:
77
+ """Decrypt a base64-encoded string encrypted with Windows DPAPI.
78
+
79
+ Args:
80
+ encrypted_b64 (str): The base64-encoded encrypted string.
81
+ scope_flags (int): The context scope for decryption (default is 4).
82
+ entropy (bytes | None): Optional entropy for decryption.
83
+
84
+ Returns:
85
+ str: The decrypted string.
86
+ """
87
+ if not encrypted_b64 or encrypted_b64.strip() == "":
88
+ logger.warning("No encrypted data provided for decryption.")
89
+ return None
90
+
91
+ # 1. Decode the Base64 string to get the raw encrypted bytes
92
+ encrypted_bytes = base64.b64decode(encrypted_b64)
93
+
94
+ # 2. Call CryptUnprotectData.
95
+ # The last parameter (flags) is set to 4
96
+ # to indicate that the data was encrypted in the machine context.
97
+ #
98
+ # The function returns a tuple: (description, decrypted_bytes)
99
+ # We only need the second element.
100
+ _, decrypted_bytes = win32crypt.CryptUnprotectData(
101
+ encrypted_bytes,
102
+ entropy, # Optional entropy
103
+ None, # Reserved
104
+ None, # Prompt Struct
105
+ scope_flags,
106
+ )
107
+
108
+ return decrypted_bytes.decode("utf-8") if decrypted_bytes else None
109
+
110
+
111
+ def win_registry_value_read(hive, subkey, value_name):
112
+ """
113
+ Reads a value from the Windows Registry.
114
+
115
+ Args:
116
+ hive: The root hive (e.g., winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER).
117
+ subkey: The path to the subkey (e.g., "SOFTWARE\\Microsoft\\Windows\\CurrentVersion").
118
+ value_name: The name of the value to read.
119
+
120
+ Returns:
121
+ The value data if found, otherwise None.
122
+ """
123
+ try:
124
+ # Open the specified registry key
125
+ key = winreg.OpenKey(hive, subkey, 0, winreg.KEY_READ)
126
+
127
+ # Query the value data
128
+ value_data, _ = winreg.QueryValueEx(key, value_name)
129
+
130
+ # Close the key
131
+ winreg.CloseKey(key)
132
+
133
+ return value_data
134
+ except FileNotFoundError:
135
+ logger.error("Registry key or value '%s\\%s' not found.", subkey, value_name)
136
+ return None
137
+ except Exception as e:
138
+ logger.error("An error occurred: %s", e)
139
+ return None
140
+
141
+
142
+ def get_win_registry_rest_pass():
143
+ """
144
+ Retrieves the base64 encrypted REST Password from the Windows Registry.
145
+
146
+ Returns:
147
+ The REST Password if found, otherwise None.
148
+ """
149
+ hive = winreg.HKEY_LOCAL_MACHINE
150
+ # HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\BigFix\Enterprise Server\MFSConfig
151
+ subkey = r"SOFTWARE\Wow6432Node\BigFix\Enterprise Server\MFSConfig"
152
+ value_name = "RESTPassword"
153
+
154
+ reg_value = win_registry_value_read(hive, subkey, value_name)
155
+
156
+ if not reg_value:
157
+ logger.error("No registry value found for RESTPassword.")
158
+ return None
159
+
160
+ # remove {obf} from start of string if present:
161
+ if reg_value and reg_value.startswith("{obf}"):
162
+ reg_value = reg_value[5:]
163
+
164
+ if reg_value:
165
+ return win_dpapi_decrypt_base64(reg_value)
166
+
167
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: besapi
3
- Version: 3.8.3
3
+ Version: 3.9.1
4
4
  Summary: Library for working with the BigFix REST API
5
5
  Home-page: https://github.com/jgstew/besapi
6
6
  Author: Matt Hansen, James Stewart
@@ -8,6 +8,7 @@ src/besapi/__init__.py
8
8
  src/besapi/__main__.py
9
9
  src/besapi/besapi.py
10
10
  src/besapi/plugin_utilities.py
11
+ src/besapi/plugin_utilities_win.py
11
12
  src/besapi.egg-info/PKG-INFO
12
13
  src/besapi.egg-info/SOURCES.txt
13
14
  src/besapi.egg-info/dependency_links.txt
@@ -25,6 +25,9 @@ if not os.getenv("TEST_PIP"):
25
25
  import besapi
26
26
  import besapi.plugin_utilities
27
27
 
28
+ if os.name == "nt":
29
+ import besapi.plugin_utilities_win
30
+
28
31
 
29
32
  def test_besapi_version():
30
33
  """Test that the besapi version is not None."""
@@ -259,9 +262,20 @@ def test_bes_conn_json():
259
262
  print(result)
260
263
  assert result is not None
261
264
  assert int(result) > 0
265
+ print("testing session_relevance_json_string tuple")
266
+ result = bes_conn.session_relevance_json_string(
267
+ '(ids of it, names of it, "TestString") of bes computers'
268
+ )
269
+ print(result)
270
+ assert result is not None
271
+ assert "TestString" in result
272
+ # NOTE: the following check is specific to my env:
273
+ if "10.0." in bes_conn.rootserver:
274
+ print("doing check specific to my env")
275
+ assert "BIGFIX" in result
262
276
 
263
277
 
264
- def test_plugin_utilities():
278
+ def test_plugin_utilities_logging():
265
279
  """Test the plugin_utilities module."""
266
280
  print(besapi.plugin_utilities.get_invoke_folder())
267
281
  print(besapi.plugin_utilities.get_invoke_file_name())
@@ -282,3 +296,72 @@ def test_plugin_utilities():
282
296
  logging.warning("Just testing to see if logging is working!")
283
297
 
284
298
  assert os.path.isfile("./tests.log")
299
+
300
+
301
+ def test_plugin_utilities_win_dpapi():
302
+ """Test the Windows DPAPI encryption function, if on Windows."""
303
+ if not os.name == "nt":
304
+ pytest.skip("Skipping Windows Registry test on non-Windows system.")
305
+
306
+ # only run this test if besapi > v3.8.3:
307
+ if besapi.besapi.__version__ <= "3.8.3":
308
+ pytest.skip("Skipping test for besapi <= 3.8.3")
309
+
310
+ test_string = "This is just a test string " + str(random.randint(0, 9999))
311
+ encrypted_str = besapi.plugin_utilities_win.win_dpapi_encrypt_str(test_string)
312
+ print("Encrypted string:", encrypted_str)
313
+ assert encrypted_str != ""
314
+ assert encrypted_str != test_string
315
+ decrypted_str = besapi.plugin_utilities_win.win_dpapi_decrypt_base64(encrypted_str)
316
+ print("Decrypted string:", decrypted_str)
317
+ assert decrypted_str == test_string
318
+
319
+
320
+ def test_plugin_utilities_win_win_registry_value_read():
321
+ """Test reading a Windows registry value."""
322
+ if not os.name == "nt":
323
+ pytest.skip("Skipping Windows Registry test on non-Windows system.")
324
+
325
+ # only run this test if besapi > v3.8.3:
326
+ if besapi.besapi.__version__ <= "3.8.3":
327
+ pytest.skip("Skipping test for besapi <= 3.8.3")
328
+
329
+ import winreg
330
+
331
+ registry_key = r"SOFTWARE\Microsoft\Windows\CurrentVersion"
332
+ registry_value = "ProgramFilesDir"
333
+ result = besapi.plugin_utilities_win.win_registry_value_read(
334
+ winreg.HKEY_LOCAL_MACHINE, registry_key, registry_value
335
+ )
336
+
337
+ assert result is not None
338
+ print("Registry value:", result)
339
+ assert "Program Files" in result
340
+
341
+
342
+ def test_plugin_utilities_win_get_win_registry_rest_pass():
343
+ """Test getting the Windows Registry REST password."""
344
+ if not os.name == "nt":
345
+ pytest.skip("Skipping Windows Registry test on non-Windows system.")
346
+
347
+ # only run this test if besapi > v3.8.3:
348
+ if besapi.besapi.__version__ <= "3.8.3":
349
+ pytest.skip("Skipping test for besapi <= 3.8.3")
350
+
351
+ import winreg
352
+
353
+ test_string = "This is just a test string " + str(random.randint(0, 9999))
354
+ encrypted_str = besapi.plugin_utilities_win.win_dpapi_encrypt_str(test_string)
355
+
356
+ # write encrypted string to registry for testing:
357
+ # HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\BigFix\Enterprise Server\MFSConfig
358
+ subkey = r"SOFTWARE\Wow6432Node\BigFix\Enterprise Server\MFSConfig"
359
+
360
+ key = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, subkey)
361
+ winreg.SetValueEx(key, "RESTPassword", 0, winreg.REG_SZ, "{obf}" + encrypted_str)
362
+ winreg.CloseKey(key)
363
+
364
+ result = besapi.plugin_utilities_win.get_win_registry_rest_pass()
365
+ assert result is not None
366
+ print("Windows Registry REST password:", result)
367
+ assert result == test_string
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes