besapi 3.8.2__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.2/src/besapi.egg-info → besapi-3.9.1}/PKG-INFO +9 -13
- {besapi-3.8.2 → besapi-3.9.1}/README.md +1 -1
- besapi-3.9.1/setup.cfg +46 -0
- besapi-3.9.1/setup.py +7 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/besapi.py +71 -34
- besapi-3.9.1/src/besapi/plugin_utilities_win.py +167 -0
- {besapi-3.8.2 → besapi-3.9.1/src/besapi.egg-info}/PKG-INFO +9 -13
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi.egg-info/SOURCES.txt +2 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/bescli/bescli.py +1 -1
- besapi-3.9.1/tests/test_besapi.py +367 -0
- {besapi-3.8.2 → besapi-3.9.1}/tests/tests.py +22 -3
- besapi-3.8.2/setup.cfg +0 -21
- besapi-3.8.2/setup.py +0 -21
- {besapi-3.8.2 → besapi-3.9.1}/LICENSE.txt +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/MANIFEST.in +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/pyproject.toml +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/__init__.py +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/__main__.py +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/plugin_utilities.py +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/schemas/BES.xsd +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/schemas/BESAPI.xsd +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi/schemas/BESActionSettings.xsd +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi.egg-info/dependency_links.txt +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi.egg-info/requires.txt +2 -2
- {besapi-3.8.2 → besapi-3.9.1}/src/besapi.egg-info/top_level.txt +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/bescli/__init__.py +0 -0
- {besapi-3.8.2 → besapi-3.9.1}/src/bescli/__main__.py +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: besapi
|
|
3
|
-
Version: 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
|
|
7
7
|
Author-email: hansen.m@psu.edu, james@jgstew.com
|
|
8
|
+
Maintainer: James Stewart
|
|
9
|
+
Maintainer-email: james@jgstew.com
|
|
8
10
|
License: MIT
|
|
9
11
|
Keywords: bigfix iem tem rest api
|
|
10
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -15,20 +17,14 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
15
17
|
Classifier: Intended Audience :: Developers
|
|
16
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
19
|
Classifier: Topic :: System :: Systems Administration
|
|
18
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.9
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
22
|
License-File: LICENSE.txt
|
|
21
|
-
Requires-Dist: requests
|
|
22
|
-
Requires-Dist: lxml
|
|
23
23
|
Requires-Dist: cmd2
|
|
24
|
+
Requires-Dist: lxml
|
|
25
|
+
Requires-Dist: requests
|
|
24
26
|
Requires-Dist: setuptools
|
|
25
|
-
Dynamic:
|
|
26
|
-
Dynamic: author-email
|
|
27
|
-
Dynamic: home-page
|
|
28
|
-
Dynamic: keywords
|
|
29
|
-
Dynamic: license
|
|
30
|
-
Dynamic: requires-dist
|
|
31
|
-
Dynamic: summary
|
|
27
|
+
Dynamic: license-file
|
|
32
28
|
|
|
33
29
|
# besapi
|
|
34
30
|
|
|
@@ -42,7 +38,7 @@ Usage:
|
|
|
42
38
|
|
|
43
39
|
```
|
|
44
40
|
import besapi
|
|
45
|
-
b = besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
|
|
41
|
+
b = besapi.besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
|
|
46
42
|
rr = b.get('sites')
|
|
47
43
|
|
|
48
44
|
# rr.request contains the original request object
|
|
@@ -10,7 +10,7 @@ Usage:
|
|
|
10
10
|
|
|
11
11
|
```
|
|
12
12
|
import besapi
|
|
13
|
-
b = besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
|
|
13
|
+
b = besapi.besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
|
|
14
14
|
rr = b.get('sites')
|
|
15
15
|
|
|
16
16
|
# rr.request contains the original request object
|
besapi-3.9.1/setup.cfg
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[metadata]
|
|
2
|
+
name = besapi
|
|
3
|
+
author = Matt Hansen, James Stewart
|
|
4
|
+
author_email = hansen.m@psu.edu, james@jgstew.com
|
|
5
|
+
maintainer = James Stewart
|
|
6
|
+
maintainer_email = james@jgstew.com
|
|
7
|
+
version = attr: besapi.besapi.__version__
|
|
8
|
+
description = Library for working with the BigFix REST API
|
|
9
|
+
keywords = bigfix iem tem rest api
|
|
10
|
+
long_description = file: README.md
|
|
11
|
+
long_description_content_type = text/markdown
|
|
12
|
+
url = https://github.com/jgstew/besapi
|
|
13
|
+
license = MIT
|
|
14
|
+
license_file = LICENSE.txt
|
|
15
|
+
classifiers =
|
|
16
|
+
Programming Language :: Python :: 3
|
|
17
|
+
Programming Language :: Python :: 3.9
|
|
18
|
+
Operating System :: OS Independent
|
|
19
|
+
License :: OSI Approved :: MIT License
|
|
20
|
+
Development Status :: 5 - Production/Stable
|
|
21
|
+
Intended Audience :: Developers
|
|
22
|
+
Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Topic :: System :: Systems Administration
|
|
24
|
+
package_dir =
|
|
25
|
+
= src
|
|
26
|
+
|
|
27
|
+
[options]
|
|
28
|
+
python_requires = >=3.9
|
|
29
|
+
include_package_data = True
|
|
30
|
+
packages = find:
|
|
31
|
+
install_requires =
|
|
32
|
+
cmd2
|
|
33
|
+
lxml
|
|
34
|
+
requests
|
|
35
|
+
setuptools
|
|
36
|
+
|
|
37
|
+
[options.package_data]
|
|
38
|
+
besapi = schemas/*.xsd
|
|
39
|
+
|
|
40
|
+
[options.packages.find]
|
|
41
|
+
where = src
|
|
42
|
+
|
|
43
|
+
[egg_info]
|
|
44
|
+
tag_build =
|
|
45
|
+
tag_date = 0
|
|
46
|
+
|
besapi-3.9.1/setup.py
ADDED
|
@@ -12,24 +12,20 @@ Library for communicating with the BES (BigFix) REST API.
|
|
|
12
12
|
import configparser
|
|
13
13
|
import datetime
|
|
14
14
|
import hashlib
|
|
15
|
+
import importlib.resources
|
|
15
16
|
import json
|
|
16
17
|
import logging
|
|
17
18
|
import os
|
|
18
19
|
import random
|
|
19
20
|
import site
|
|
20
21
|
import string
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
try:
|
|
24
|
-
from urllib import parse
|
|
25
|
-
except ImportError:
|
|
26
|
-
from urlparse import parse_qs as parse # type: ignore[no-redef,import-not-found]
|
|
22
|
+
import urllib.parse
|
|
27
23
|
|
|
24
|
+
import lxml.etree
|
|
25
|
+
import lxml.objectify
|
|
28
26
|
import requests
|
|
29
|
-
from lxml import etree, objectify
|
|
30
|
-
from pkg_resources import resource_filename
|
|
31
27
|
|
|
32
|
-
__version__ = "3.
|
|
28
|
+
__version__ = "3.9.1"
|
|
33
29
|
|
|
34
30
|
besapi_logger = logging.getLogger("besapi")
|
|
35
31
|
|
|
@@ -48,7 +44,7 @@ def rand_password(length=20):
|
|
|
48
44
|
|
|
49
45
|
def sanitize_txt(*args):
|
|
50
46
|
"""Clean arbitrary text for safe file system usage."""
|
|
51
|
-
valid_chars = "-_.()
|
|
47
|
+
valid_chars = f"-_.() {string.ascii_letters}{string.digits}"
|
|
52
48
|
|
|
53
49
|
sani_args = []
|
|
54
50
|
for arg in args:
|
|
@@ -158,7 +154,7 @@ def parse_bes_modtime(string_datetime):
|
|
|
158
154
|
# )
|
|
159
155
|
|
|
160
156
|
|
|
161
|
-
def get_action_combined_relevance(relevances:
|
|
157
|
+
def get_action_combined_relevance(relevances: list[str]):
|
|
162
158
|
"""Take array of ordered relevance clauses and return relevance string for
|
|
163
159
|
action.
|
|
164
160
|
"""
|
|
@@ -249,17 +245,19 @@ def get_target_xml(targets=None):
|
|
|
249
245
|
def validate_xsd(doc):
|
|
250
246
|
"""Validate results using XML XSDs."""
|
|
251
247
|
try:
|
|
252
|
-
xmldoc = etree.fromstring(doc)
|
|
248
|
+
xmldoc = lxml.etree.fromstring(doc)
|
|
253
249
|
except BaseException: # pylint: disable=broad-except
|
|
254
250
|
return False
|
|
255
251
|
|
|
256
252
|
for xsd in ["BES.xsd", "BESAPI.xsd", "BESActionSettings.xsd"]:
|
|
257
|
-
|
|
253
|
+
schema_path = importlib.resources.files(__package__) / f"schemas/{xsd}"
|
|
254
|
+
with schema_path.open("r") as xsd_file:
|
|
255
|
+
xmlschema_doc = lxml.etree.parse(xsd_file)
|
|
258
256
|
|
|
259
257
|
# one schema may throw an error while another will validate
|
|
260
258
|
try:
|
|
261
|
-
xmlschema = etree.XMLSchema(xmlschema_doc)
|
|
262
|
-
except etree.XMLSchemaParseError as err:
|
|
259
|
+
xmlschema = lxml.etree.XMLSchema(xmlschema_doc)
|
|
260
|
+
except lxml.etree.XMLSchemaParseError as err:
|
|
263
261
|
# this should only error if the XSD itself is malformed
|
|
264
262
|
besapi_logger.error("ERROR with `%s`: %s", xsd, err)
|
|
265
263
|
raise err
|
|
@@ -392,7 +390,7 @@ class BESConnection:
|
|
|
392
390
|
if path.startswith(self.rootserver):
|
|
393
391
|
url = path
|
|
394
392
|
else:
|
|
395
|
-
url = "
|
|
393
|
+
url = f"{self.rootserver}/api/{path}"
|
|
396
394
|
|
|
397
395
|
return url
|
|
398
396
|
|
|
@@ -414,9 +412,9 @@ class BESConnection:
|
|
|
414
412
|
if validate_xml:
|
|
415
413
|
logging.error(err_msg)
|
|
416
414
|
raise ValueError(err_msg)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
415
|
+
|
|
416
|
+
# this is intended it validate_xml is None, but not used currently
|
|
417
|
+
logging.warning(err_msg)
|
|
420
418
|
|
|
421
419
|
self.last_connected = datetime.datetime.now()
|
|
422
420
|
return RESTResult(
|
|
@@ -435,9 +433,9 @@ class BESConnection:
|
|
|
435
433
|
if validate_xml:
|
|
436
434
|
logging.error(err_msg)
|
|
437
435
|
raise ValueError(err_msg)
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
436
|
+
|
|
437
|
+
# this is intended it validate_xml is None, but not used currently
|
|
438
|
+
logging.warning(err_msg)
|
|
441
439
|
|
|
442
440
|
return RESTResult(
|
|
443
441
|
self.session.put(self.url(path), data=data, verify=self.verify, **kwargs)
|
|
@@ -450,13 +448,50 @@ class BESConnection:
|
|
|
450
448
|
self.session.delete(self.url(path), verify=self.verify, **kwargs)
|
|
451
449
|
)
|
|
452
450
|
|
|
451
|
+
def session_relevance_json(self, relevance, **kwargs):
|
|
452
|
+
"""Get Session Relevance Results in JSON.
|
|
453
|
+
|
|
454
|
+
This will submit the relevance string as json instead of html form data.
|
|
455
|
+
"""
|
|
456
|
+
session_relevance = urllib.parse.quote(relevance, safe=":+")
|
|
457
|
+
rel_data = {"output": "json", "relevance": session_relevance}
|
|
458
|
+
self.last_connected = datetime.datetime.now()
|
|
459
|
+
result = RESTResult(
|
|
460
|
+
self.session.post(
|
|
461
|
+
self.url("query"),
|
|
462
|
+
data=rel_data,
|
|
463
|
+
verify=self.verify,
|
|
464
|
+
**kwargs,
|
|
465
|
+
)
|
|
466
|
+
)
|
|
467
|
+
return json.loads(result.text)
|
|
468
|
+
|
|
469
|
+
def session_relevance_json_array(self, relevance, **kwargs):
|
|
470
|
+
"""Get Session Relevance Results in an array from the json return.
|
|
471
|
+
|
|
472
|
+
This will submit the relevance string as json instead of html form data.
|
|
473
|
+
"""
|
|
474
|
+
result = self.session_relevance_json(relevance, **kwargs)
|
|
475
|
+
return result["result"]
|
|
476
|
+
|
|
477
|
+
def session_relevance_json_string(self, relevance, **kwargs):
|
|
478
|
+
"""Get Session Relevance Results in a string from the json return.
|
|
479
|
+
|
|
480
|
+
This will submit the relevance string as json instead of html form data.
|
|
481
|
+
"""
|
|
482
|
+
# not sure if the following is needed to handle some cases:
|
|
483
|
+
# relevance = "(it as string) of ( " + relevance + " )"
|
|
484
|
+
rel_result_array = self.session_relevance_json_array(relevance, **kwargs)
|
|
485
|
+
# Ensure each element is converted to a string
|
|
486
|
+
return "\n".join(map(str, rel_result_array))
|
|
487
|
+
|
|
453
488
|
def session_relevance_xml(self, relevance, **kwargs):
|
|
454
489
|
"""Get Session Relevance Results XML."""
|
|
455
490
|
self.last_connected = datetime.datetime.now()
|
|
456
491
|
return RESTResult(
|
|
457
492
|
self.session.post(
|
|
458
493
|
self.url("query"),
|
|
459
|
-
data=f"relevance={parse.quote(relevance, safe=':+')}",
|
|
494
|
+
data=f"relevance={urllib.parse.quote(relevance, safe=':+')}",
|
|
460
495
|
verify=self.verify,
|
|
461
496
|
**kwargs,
|
|
462
497
|
)
|
|
@@ -626,6 +661,8 @@ class BESConnection:
|
|
|
626
661
|
self.site_path = site_path
|
|
627
662
|
return self.site_path
|
|
628
663
|
|
|
664
|
+
return None
|
|
665
|
+
|
|
629
666
|
def import_bes_to_site(self, bes_file_path, site_path=None):
|
|
630
667
|
"""Import bes file to site."""
|
|
631
668
|
|
|
@@ -651,7 +688,7 @@ class BESConnection:
|
|
|
651
688
|
|
|
652
689
|
def create_site_from_file(self, bes_file_path, site_type="custom"):
|
|
653
690
|
"""Create new site."""
|
|
654
|
-
xml_parsed = etree.parse(bes_file_path)
|
|
691
|
+
xml_parsed = lxml.etree.parse(bes_file_path)
|
|
655
692
|
new_site_name = xml_parsed.xpath("/BES/CustomSite/Name/text()")[0]
|
|
656
693
|
|
|
657
694
|
result_site_path = self.validate_site_path(
|
|
@@ -662,7 +699,7 @@ class BESConnection:
|
|
|
662
699
|
besapi_logger.warning("Site `%s` already exists", result_site_path)
|
|
663
700
|
return None
|
|
664
701
|
|
|
665
|
-
result_site = self.post("sites", etree.tostring(xml_parsed))
|
|
702
|
+
result_site = self.post("sites", lxml.etree.tostring(xml_parsed))
|
|
666
703
|
|
|
667
704
|
return result_site
|
|
668
705
|
|
|
@@ -678,7 +715,7 @@ class BESConnection:
|
|
|
678
715
|
|
|
679
716
|
def create_user_from_file(self, bes_file_path):
|
|
680
717
|
"""Create user from xml."""
|
|
681
|
-
xml_parsed = etree.parse(bes_file_path)
|
|
718
|
+
xml_parsed = lxml.etree.parse(bes_file_path)
|
|
682
719
|
new_user_name = xml_parsed.xpath("/BESAPI/Operator/Name/text()")[0]
|
|
683
720
|
result_user = self.get_user(new_user_name)
|
|
684
721
|
|
|
@@ -686,7 +723,7 @@ class BESConnection:
|
|
|
686
723
|
besapi_logger.warning("User `%s` Already Exists!", new_user_name)
|
|
687
724
|
return result_user
|
|
688
725
|
besapi_logger.info("Creating User `%s`", new_user_name)
|
|
689
|
-
_ = self.post("operators", etree.tostring(xml_parsed))
|
|
726
|
+
_ = self.post("operators", lxml.etree.tostring(xml_parsed))
|
|
690
727
|
# print(user_result)
|
|
691
728
|
return self.get_user(new_user_name)
|
|
692
729
|
|
|
@@ -708,7 +745,7 @@ class BESConnection:
|
|
|
708
745
|
def create_group_from_file(self, bes_file_path, site_path=None):
|
|
709
746
|
"""Create a new group."""
|
|
710
747
|
site_path = self.get_current_site_path(site_path)
|
|
711
|
-
xml_parsed = etree.parse(bes_file_path)
|
|
748
|
+
xml_parsed = lxml.etree.parse(bes_file_path)
|
|
712
749
|
new_group_name = xml_parsed.xpath("/BES/ComputerGroup/Title/text()")[0]
|
|
713
750
|
|
|
714
751
|
existing_group = self.get_computergroup(new_group_name, site_path)
|
|
@@ -719,7 +756,7 @@ class BESConnection:
|
|
|
719
756
|
|
|
720
757
|
# print(lxml.etree.tostring(xml_parsed))
|
|
721
758
|
|
|
722
|
-
_ = self.post(f"computergroups/{site_path}", etree.tostring(xml_parsed))
|
|
759
|
+
_ = self.post(f"computergroups/{site_path}", lxml.etree.tostring(xml_parsed))
|
|
723
760
|
|
|
724
761
|
return self.get_computergroup(site_path, new_group_name)
|
|
725
762
|
|
|
@@ -844,7 +881,7 @@ class BESConnection:
|
|
|
844
881
|
def update_item_from_file(self, file_path, site_path=None):
|
|
845
882
|
"""Update an item by name and last modified."""
|
|
846
883
|
site_path = self.get_current_site_path(site_path)
|
|
847
|
-
bes_tree = etree.parse(file_path)
|
|
884
|
+
bes_tree = lxml.etree.parse(file_path)
|
|
848
885
|
|
|
849
886
|
with open(file_path, "rb") as f:
|
|
850
887
|
content = f.read()
|
|
@@ -1130,7 +1167,7 @@ class RESTResult:
|
|
|
1130
1167
|
"""Property for python dict representation."""
|
|
1131
1168
|
if self._besdict is None:
|
|
1132
1169
|
if self.valid:
|
|
1133
|
-
self._besdict = elem2dict(etree.fromstring(self.besxml))
|
|
1170
|
+
self._besdict = elem2dict(lxml.etree.fromstring(self.besxml))
|
|
1134
1171
|
else:
|
|
1135
1172
|
self._besdict = {"text": str(self)}
|
|
1136
1173
|
|
|
@@ -1154,11 +1191,11 @@ class RESTResult:
|
|
|
1154
1191
|
def xmlparse_text(self, text):
|
|
1155
1192
|
"""Parse response text as xml."""
|
|
1156
1193
|
if type(text) is str:
|
|
1157
|
-
root_xml = etree.fromstring(text.encode("utf-8"))
|
|
1194
|
+
root_xml = lxml.etree.fromstring(text.encode("utf-8"))
|
|
1158
1195
|
else:
|
|
1159
1196
|
root_xml = text
|
|
1160
1197
|
|
|
1161
|
-
return etree.tostring(root_xml, encoding="utf-8", xml_declaration=True)
|
|
1198
|
+
return lxml.etree.tostring(root_xml, encoding="utf-8", xml_declaration=True)
|
|
1162
1199
|
|
|
1163
1200
|
def objectify_text(self, text):
|
|
1164
1201
|
"""Parse response text as objectified xml."""
|
|
@@ -1167,7 +1204,7 @@ class RESTResult:
|
|
|
1167
1204
|
else:
|
|
1168
1205
|
root_xml = text
|
|
1169
1206
|
|
|
1170
|
-
return objectify.fromstring(root_xml)
|
|
1207
|
+
return lxml.objectify.fromstring(root_xml)
|
|
1171
1208
|
|
|
1172
1209
|
|
|
1173
1210
|
def main():
|
|
@@ -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,10 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: besapi
|
|
3
|
-
Version: 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
|
|
7
7
|
Author-email: hansen.m@psu.edu, james@jgstew.com
|
|
8
|
+
Maintainer: James Stewart
|
|
9
|
+
Maintainer-email: james@jgstew.com
|
|
8
10
|
License: MIT
|
|
9
11
|
Keywords: bigfix iem tem rest api
|
|
10
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -15,20 +17,14 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
15
17
|
Classifier: Intended Audience :: Developers
|
|
16
18
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
19
|
Classifier: Topic :: System :: Systems Administration
|
|
18
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.9
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
22
|
License-File: LICENSE.txt
|
|
21
|
-
Requires-Dist: requests
|
|
22
|
-
Requires-Dist: lxml
|
|
23
23
|
Requires-Dist: cmd2
|
|
24
|
+
Requires-Dist: lxml
|
|
25
|
+
Requires-Dist: requests
|
|
24
26
|
Requires-Dist: setuptools
|
|
25
|
-
Dynamic:
|
|
26
|
-
Dynamic: author-email
|
|
27
|
-
Dynamic: home-page
|
|
28
|
-
Dynamic: keywords
|
|
29
|
-
Dynamic: license
|
|
30
|
-
Dynamic: requires-dist
|
|
31
|
-
Dynamic: summary
|
|
27
|
+
Dynamic: license-file
|
|
32
28
|
|
|
33
29
|
# besapi
|
|
34
30
|
|
|
@@ -42,7 +38,7 @@ Usage:
|
|
|
42
38
|
|
|
43
39
|
```
|
|
44
40
|
import besapi
|
|
45
|
-
b = besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
|
|
41
|
+
b = besapi.besapi.BESConnection('my_username', 'my_password', 'https://rootserver.domain.org:52311')
|
|
46
42
|
rr = b.get('sites')
|
|
47
43
|
|
|
48
44
|
# rr.request contains the original request object
|
|
@@ -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
|
|
@@ -19,4 +20,5 @@ src/besapi/schemas/BESActionSettings.xsd
|
|
|
19
20
|
src/bescli/__init__.py
|
|
20
21
|
src/bescli/__main__.py
|
|
21
22
|
src/bescli/bescli.py
|
|
23
|
+
tests/test_besapi.py
|
|
22
24
|
tests/tests.py
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Test besapi with pytest.
|
|
3
|
+
|
|
4
|
+
This was converted from tests/tests.py which was used before this pytest was added.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import random
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
15
|
+
# mypy: disable-error-code="arg-type"
|
|
16
|
+
|
|
17
|
+
if not os.getenv("TEST_PIP"):
|
|
18
|
+
# add module folder to import paths for testing local src
|
|
19
|
+
sys.path.append(
|
|
20
|
+
os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src")
|
|
21
|
+
)
|
|
22
|
+
# reverse the order so we make sure to get the local src module
|
|
23
|
+
sys.path.reverse()
|
|
24
|
+
|
|
25
|
+
import besapi
|
|
26
|
+
import besapi.plugin_utilities
|
|
27
|
+
|
|
28
|
+
if os.name == "nt":
|
|
29
|
+
import besapi.plugin_utilities_win
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_besapi_version():
|
|
33
|
+
"""Test that the besapi version is not None."""
|
|
34
|
+
assert besapi.besapi.__version__ is not None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_rand_password():
|
|
38
|
+
"""Test that the generated random password has the correct length."""
|
|
39
|
+
assert 15 == len(besapi.besapi.rand_password(15))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_sanitize_txt():
|
|
43
|
+
"""Test that the sanitize_txt function works correctly."""
|
|
44
|
+
assert ("test--string", "test") == besapi.besapi.sanitize_txt(
|
|
45
|
+
r"test/\string", "test%"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_replace_text_between():
|
|
50
|
+
"""Test that the replace_text_between function works correctly."""
|
|
51
|
+
assert "http://localhost:52311/file.example" == besapi.besapi.replace_text_between(
|
|
52
|
+
"http://example:52311/file.example", "://", ":52311", "localhost"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_validate_site_path():
|
|
57
|
+
"""Test the validate_site_path function with various inputs."""
|
|
58
|
+
assert "master" in besapi.besapi.BESConnection.validate_site_path(
|
|
59
|
+
"", "master", False
|
|
60
|
+
)
|
|
61
|
+
assert "custom/" in besapi.besapi.BESConnection.validate_site_path(
|
|
62
|
+
"", "custom/Example", False
|
|
63
|
+
)
|
|
64
|
+
assert "operator/" in besapi.besapi.BESConnection.validate_site_path(
|
|
65
|
+
"", "operator/Example", False
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_validate_xml_bes_file():
|
|
70
|
+
"""Test the validate_xml_bes_file function with good and bad files."""
|
|
71
|
+
assert besapi.besapi.validate_xml_bes_file("tests/good/RelaySelectTask.bes") is True
|
|
72
|
+
assert (
|
|
73
|
+
besapi.besapi.validate_xml_bes_file("tests/good/ComputerGroupsExample.bes")
|
|
74
|
+
is True
|
|
75
|
+
)
|
|
76
|
+
assert (
|
|
77
|
+
besapi.besapi.validate_xml_bes_file("tests/bad/RelaySelectTask_BAD.bes")
|
|
78
|
+
is False
|
|
79
|
+
)
|
|
80
|
+
assert (
|
|
81
|
+
besapi.besapi.validate_xml_bes_file("tests/bad/ComputerGroups_BAD.bes") is False
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_failing_validate_site_path():
|
|
86
|
+
"""Test that validate_site_path raises ValueError for invalid inputs."""
|
|
87
|
+
|
|
88
|
+
with pytest.raises(ValueError):
|
|
89
|
+
besapi.besapi.BESConnection.validate_site_path("", "bad/Example", False)
|
|
90
|
+
|
|
91
|
+
with pytest.raises(ValueError):
|
|
92
|
+
besapi.besapi.BESConnection.validate_site_path("", "bad/master", False)
|
|
93
|
+
|
|
94
|
+
with pytest.raises(ValueError):
|
|
95
|
+
besapi.besapi.BESConnection.validate_site_path("", "", False, True)
|
|
96
|
+
|
|
97
|
+
with pytest.raises(ValueError):
|
|
98
|
+
besapi.besapi.BESConnection.validate_site_path("", None, False, True)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RequestResult:
|
|
102
|
+
text = "this is just a test"
|
|
103
|
+
headers: list = []
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_rest_result():
|
|
107
|
+
"""Test the RESTResult class."""
|
|
108
|
+
request_result = RequestResult()
|
|
109
|
+
rest_result = besapi.besapi.RESTResult(request_result)
|
|
110
|
+
|
|
111
|
+
assert rest_result.besdict is not None
|
|
112
|
+
assert rest_result.besjson is not None
|
|
113
|
+
assert b"<BES>Example</BES>" in rest_result.xmlparse_text("<BES>Example</BES>")
|
|
114
|
+
assert rest_result.text == "this is just a test"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_parse_bes_modtime():
|
|
118
|
+
"""Test the parse_bes_modtime function."""
|
|
119
|
+
assert (
|
|
120
|
+
2017 == besapi.besapi.parse_bes_modtime("Tue, 05 Sep 2017 23:31:48 +0000").year
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_get_action_combined_relevance():
|
|
125
|
+
"""Test the get_action_combined_relevance function."""
|
|
126
|
+
assert (
|
|
127
|
+
"( ( True ) AND ( windows of operating system ) ) AND ( False )"
|
|
128
|
+
== besapi.besapi.get_action_combined_relevance(
|
|
129
|
+
["True", "windows of operating system", "False"]
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_get_target_xml():
|
|
135
|
+
"""Test the get_target_xml function with various inputs."""
|
|
136
|
+
assert "<CustomRelevance>False</CustomRelevance>" == besapi.besapi.get_target_xml()
|
|
137
|
+
assert "<AllComputers>true</AllComputers>" == besapi.besapi.get_target_xml(
|
|
138
|
+
"<AllComputers>"
|
|
139
|
+
)
|
|
140
|
+
assert "<ComputerID>1</ComputerID>" == besapi.besapi.get_target_xml(1)
|
|
141
|
+
assert (
|
|
142
|
+
"<CustomRelevance><![CDATA[not windows of operating system]]></CustomRelevance>"
|
|
143
|
+
== besapi.besapi.get_target_xml("not windows of operating system")
|
|
144
|
+
)
|
|
145
|
+
assert (
|
|
146
|
+
"<ComputerID>1</ComputerID><ComputerID>2</ComputerID>"
|
|
147
|
+
== besapi.besapi.get_target_xml([1, 2])
|
|
148
|
+
)
|
|
149
|
+
assert (
|
|
150
|
+
"<ComputerName>Computer 1</ComputerName><ComputerName>Another Computer</ComputerName>"
|
|
151
|
+
== besapi.besapi.get_target_xml(["Computer 1", "Another Computer"])
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_bescli():
|
|
156
|
+
"""Test the BESCLInterface class and its methods."""
|
|
157
|
+
import bescli
|
|
158
|
+
|
|
159
|
+
bigfix_cli = bescli.bescli.BESCLInterface()
|
|
160
|
+
|
|
161
|
+
# just make sure these don't throw errors:
|
|
162
|
+
bigfix_cli.do_ls()
|
|
163
|
+
bigfix_cli.do_clear()
|
|
164
|
+
bigfix_cli.do_ls()
|
|
165
|
+
bigfix_cli.do_logout()
|
|
166
|
+
bigfix_cli.do_error_count()
|
|
167
|
+
bigfix_cli.do_version()
|
|
168
|
+
bigfix_cli.do_conf()
|
|
169
|
+
|
|
170
|
+
# this should really only run if the config file is present:
|
|
171
|
+
if bigfix_cli.bes_conn:
|
|
172
|
+
# session relevance tests require functioning web reports server
|
|
173
|
+
assert (
|
|
174
|
+
int(bigfix_cli.bes_conn.session_relevance_string("number of bes computers"))
|
|
175
|
+
> 0
|
|
176
|
+
)
|
|
177
|
+
assert (
|
|
178
|
+
"test session relevance string result"
|
|
179
|
+
in bigfix_cli.bes_conn.session_relevance_string(
|
|
180
|
+
'"test session relevance string result"'
|
|
181
|
+
)
|
|
182
|
+
)
|
|
183
|
+
bigfix_cli.do_set_current_site("master")
|
|
184
|
+
|
|
185
|
+
# set working directory to folder this file is in:
|
|
186
|
+
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
|
187
|
+
|
|
188
|
+
# set working directory to src folder in parent folder
|
|
189
|
+
os.chdir("../src")
|
|
190
|
+
|
|
191
|
+
# Test file upload:
|
|
192
|
+
upload_result = bigfix_cli.bes_conn.upload(
|
|
193
|
+
"./besapi/__init__.py", "test_besapi_upload.txt"
|
|
194
|
+
)
|
|
195
|
+
# print(upload_result)
|
|
196
|
+
assert upload_result.besobj.FileUpload.Available == 1
|
|
197
|
+
assert upload_result.besdict["FileUpload"]["Available"] == "1"
|
|
198
|
+
assert upload_result.besjson is not None
|
|
199
|
+
upload_result_json = json.loads(upload_result.besjson)
|
|
200
|
+
assert upload_result_json["FileUpload"]["Available"] == "1"
|
|
201
|
+
|
|
202
|
+
assert "test_besapi_upload.txt</URL>" in str(upload_result)
|
|
203
|
+
upload_prefetch = bigfix_cli.bes_conn.parse_upload_result_to_prefetch(
|
|
204
|
+
upload_result
|
|
205
|
+
)
|
|
206
|
+
# print(upload_prefetch)
|
|
207
|
+
assert "prefetch test_besapi_upload.txt sha1:" in upload_prefetch
|
|
208
|
+
|
|
209
|
+
dashboard_name = "_PyBESAPI_tests.py"
|
|
210
|
+
var_name = "TestVarName"
|
|
211
|
+
var_value = "TestVarValue " + str(random.randint(0, 9999))
|
|
212
|
+
|
|
213
|
+
assert var_value in str(
|
|
214
|
+
bigfix_cli.bes_conn.set_dashboard_variable_value(
|
|
215
|
+
dashboard_name, var_name, var_value
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
assert var_value in str(
|
|
220
|
+
bigfix_cli.bes_conn.get_dashboard_variable_value(dashboard_name, var_name)
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if os.name == "nt":
|
|
224
|
+
subprocess.run(
|
|
225
|
+
'CMD /C python -m besapi ls clear ls conf "query number of bes computers" version error_count exit',
|
|
226
|
+
check=True,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
bes_conn = besapi.besapi.get_bes_conn_using_config_file()
|
|
230
|
+
print("login succeeded:", bes_conn.login())
|
|
231
|
+
assert bes_conn.login()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def test_bes_conn_json():
|
|
235
|
+
"""Test the BESConnection class with JSON output."""
|
|
236
|
+
bes_conn = besapi.besapi.get_bes_conn_using_config_file()
|
|
237
|
+
if bes_conn and bes_conn.login():
|
|
238
|
+
print("testing session_relevance_json")
|
|
239
|
+
result = bes_conn.session_relevance_json("number of bes computers")
|
|
240
|
+
assert result is not None
|
|
241
|
+
assert int(result["result"][0]) > 0
|
|
242
|
+
result = bes_conn.session_relevance_json(
|
|
243
|
+
"""("[%22" & it & "%22]") of concatenation "%22, %22" of names of bes computers"""
|
|
244
|
+
)
|
|
245
|
+
assert result is not None
|
|
246
|
+
string_first_result_json = result["result"][0]
|
|
247
|
+
print(string_first_result_json)
|
|
248
|
+
assert '", "' in string_first_result_json
|
|
249
|
+
assert '["' in string_first_result_json
|
|
250
|
+
# NOTE: the following check is specific to my env:
|
|
251
|
+
if "10.0." in bes_conn.rootserver:
|
|
252
|
+
print("doing check specific to my env")
|
|
253
|
+
assert '", "BIGFIX", "' in string_first_result_json
|
|
254
|
+
|
|
255
|
+
print("testing session_relevance_json_array")
|
|
256
|
+
result = bes_conn.session_relevance_json_array("number of bes computers")
|
|
257
|
+
print(result)
|
|
258
|
+
assert result is not None
|
|
259
|
+
assert int(result[0]) > 0
|
|
260
|
+
print("testing session_relevance_json_string")
|
|
261
|
+
result = bes_conn.session_relevance_json_string("number of bes computers")
|
|
262
|
+
print(result)
|
|
263
|
+
assert result is not None
|
|
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
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def test_plugin_utilities_logging():
|
|
279
|
+
"""Test the plugin_utilities module."""
|
|
280
|
+
print(besapi.plugin_utilities.get_invoke_folder())
|
|
281
|
+
print(besapi.plugin_utilities.get_invoke_file_name())
|
|
282
|
+
|
|
283
|
+
parser = besapi.plugin_utilities.setup_plugin_argparse(plugin_args_required=False)
|
|
284
|
+
# allow unknown args to be parsed instead of throwing an error:
|
|
285
|
+
args, _unknown = parser.parse_known_args()
|
|
286
|
+
|
|
287
|
+
# test logging plugin_utilities:
|
|
288
|
+
import logging
|
|
289
|
+
|
|
290
|
+
logging_config = besapi.plugin_utilities.get_plugin_logging_config("./tests.log")
|
|
291
|
+
|
|
292
|
+
# this use of logging.basicConfig requires python >= 3.9
|
|
293
|
+
if sys.version_info >= (3, 9):
|
|
294
|
+
logging.basicConfig(**logging_config)
|
|
295
|
+
|
|
296
|
+
logging.warning("Just testing to see if logging is working!")
|
|
297
|
+
|
|
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
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"""Test besapi."""
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
5
|
+
import json
|
|
5
6
|
import os
|
|
6
7
|
import random
|
|
7
8
|
import subprocess
|
|
@@ -46,6 +47,13 @@ assert "operator/" in besapi.besapi.BESConnection.validate_site_path(
|
|
|
46
47
|
"", "operator/Example", False
|
|
47
48
|
)
|
|
48
49
|
|
|
50
|
+
assert besapi.besapi.validate_xml_bes_file("tests/good/RelaySelectTask.bes") is True
|
|
51
|
+
assert (
|
|
52
|
+
besapi.besapi.validate_xml_bes_file("tests/good/ComputerGroupsExample.bes") is True
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
assert besapi.besapi.validate_xml_bes_file("tests/bad/RelaySelectTask_BAD.bes") is False
|
|
56
|
+
|
|
49
57
|
# start failing tests:
|
|
50
58
|
raised_errors = 0
|
|
51
59
|
|
|
@@ -77,9 +85,9 @@ assert raised_errors == 4
|
|
|
77
85
|
# end failing tests
|
|
78
86
|
|
|
79
87
|
|
|
80
|
-
class RequestResult
|
|
88
|
+
class RequestResult:
|
|
81
89
|
text = "this is just a test"
|
|
82
|
-
headers = []
|
|
90
|
+
headers: list = []
|
|
83
91
|
|
|
84
92
|
|
|
85
93
|
request_result = RequestResult()
|
|
@@ -159,9 +167,20 @@ if bigfix_cli.bes_conn:
|
|
|
159
167
|
upload_result = bigfix_cli.bes_conn.upload(
|
|
160
168
|
"./besapi/__init__.py", "test_besapi_upload.txt"
|
|
161
169
|
)
|
|
170
|
+
|
|
162
171
|
# print(upload_result)
|
|
172
|
+
# print(upload_result.besobj.FileUpload.Available)
|
|
173
|
+
assert upload_result.besobj.FileUpload.Available == 1
|
|
163
174
|
assert "test_besapi_upload.txt</URL>" in str(upload_result)
|
|
164
|
-
|
|
175
|
+
upload_result_json = json.loads(upload_result.besjson)
|
|
176
|
+
# print(upload_result_json["FileUpload"]["Available"])
|
|
177
|
+
assert upload_result_json["FileUpload"]["Available"] == "1"
|
|
178
|
+
|
|
179
|
+
upload_prefetch = bigfix_cli.bes_conn.parse_upload_result_to_prefetch(upload_result)
|
|
180
|
+
# print(upload_prefetch)
|
|
181
|
+
assert "prefetch test_besapi_upload.txt sha1:" in upload_prefetch
|
|
182
|
+
assert "test_besapi_upload.txt" in str(upload_result)
|
|
183
|
+
# print(bigfix_cli.bes_conn.parse_upload_result_to_prefetch(upload_result))
|
|
165
184
|
|
|
166
185
|
dashboard_name = "_PyBESAPI_tests.py"
|
|
167
186
|
var_name = "TestVarName"
|
besapi-3.8.2/setup.cfg
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
[metadata]
|
|
2
|
-
version = attr: besapi.besapi.__version__
|
|
3
|
-
long_description = file: README.md
|
|
4
|
-
long_description_content_type = text/markdown
|
|
5
|
-
classifiers =
|
|
6
|
-
Programming Language :: Python :: 3
|
|
7
|
-
Programming Language :: Python :: 3.9
|
|
8
|
-
Operating System :: OS Independent
|
|
9
|
-
License :: OSI Approved :: MIT License
|
|
10
|
-
Development Status :: 5 - Production/Stable
|
|
11
|
-
Intended Audience :: Developers
|
|
12
|
-
Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
-
Topic :: System :: Systems Administration
|
|
14
|
-
|
|
15
|
-
[options]
|
|
16
|
-
python_requires = >=3.7
|
|
17
|
-
|
|
18
|
-
[egg_info]
|
|
19
|
-
tag_build =
|
|
20
|
-
tag_date = 0
|
|
21
|
-
|
besapi-3.8.2/setup.py
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
"""File to configure python build and packaging for pip."""
|
|
3
|
-
|
|
4
|
-
import setuptools
|
|
5
|
-
|
|
6
|
-
setuptools.setup(
|
|
7
|
-
name="besapi",
|
|
8
|
-
# version= moved to setup.cfg
|
|
9
|
-
author="Matt Hansen, James Stewart",
|
|
10
|
-
author_email="hansen.m@psu.edu, james@jgstew.com",
|
|
11
|
-
description="Library for working with the BigFix REST API",
|
|
12
|
-
license="MIT",
|
|
13
|
-
keywords="bigfix iem tem rest api",
|
|
14
|
-
url="https://github.com/jgstew/besapi",
|
|
15
|
-
# long_description= moved to setup.cfg
|
|
16
|
-
packages=["besapi", "bescli"],
|
|
17
|
-
package_data={"besapi": ["schemas/*.xsd"]},
|
|
18
|
-
install_requires=["requests", "lxml", "cmd2", "setuptools"],
|
|
19
|
-
include_package_data=True,
|
|
20
|
-
package_dir={"": "src"},
|
|
21
|
-
)
|
|
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
|