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.
- {besapi-3.8.3/src/besapi.egg-info → besapi-3.9.1}/PKG-INFO +1 -1
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/besapi.py +3 -1
- besapi-3.9.1/src/besapi/plugin_utilities_win.py +167 -0
- {besapi-3.8.3 → besapi-3.9.1/src/besapi.egg-info}/PKG-INFO +1 -1
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/SOURCES.txt +1 -0
- {besapi-3.8.3 → besapi-3.9.1}/tests/test_besapi.py +84 -1
- {besapi-3.8.3 → besapi-3.9.1}/LICENSE.txt +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/MANIFEST.in +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/README.md +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/pyproject.toml +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/setup.cfg +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/setup.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/__init__.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/__main__.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/plugin_utilities.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/schemas/BES.xsd +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/schemas/BESAPI.xsd +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi/schemas/BESActionSettings.xsd +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/dependency_links.txt +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/requires.txt +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/besapi.egg-info/top_level.txt +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/bescli/__init__.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/bescli/__main__.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/src/bescli/bescli.py +0 -0
- {besapi-3.8.3 → besapi-3.9.1}/tests/tests.py +0 -0
|
@@ -25,7 +25,7 @@ import lxml.etree
|
|
|
25
25
|
import lxml.objectify
|
|
26
26
|
import requests
|
|
27
27
|
|
|
28
|
-
__version__ = "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
|
|
@@ -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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|