Skip to content

Commit 4919251

Browse files
authored
feat: implemented new Report Definitions API endpoint (#82)
1 parent 063968a commit 4919251

File tree

4 files changed

+320
-10
lines changed

4 files changed

+320
-10
lines changed

laceworksdk/api/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from .v2.organization_info import OrganizationInfoAPI
4141
from .v2.policies import PoliciesAPI
4242
from .v2.queries import QueriesAPI
43+
from .v2.report_definitions import ReportDefinitionsAPI
4344
from .v2.report_rules import ReportRulesAPI
4445
from .v2.reports import ReportsAPI
4546
from .v2.resource_groups import ResourceGroupsAPI
@@ -161,6 +162,7 @@ def __init__(self,
161162
self.policies = PoliciesAPI(self._session)
162163
self.queries = QueriesAPI(self._session)
163164
self.recommendations = RecommendationsAPI(self._session)
165+
self.report_definitions = ReportDefinitionsAPI(self._session)
164166
self.report_rules = ReportRulesAPI(self._session)
165167
self.reports = ReportsAPI(self._session)
166168
self.resource_groups = ResourceGroupsAPI(self._session)

laceworksdk/api/base_endpoint.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,15 @@ def build_dict_from_items(self, *dicts, **items):
2424
"""
2525
A method to build a dictionary based on inputs, pruning items that are None.
2626
27-
:raises KeyError: In case there is a duplicate key name in the dictionary.
2827
:returns: A single dict built from the input.
2928
"""
3029

3130
dict_list = list(dicts)
3231
dict_list.append(items)
3332
result = {}
3433

35-
for d in dict_list:
36-
for key, value in d.items():
37-
camel_key = self._convert_lower_camel_case(key)
38-
if value is None:
39-
continue
40-
if camel_key in result.keys():
41-
raise KeyError(f"Attempted to insert duplicate key '{camel_key}'")
42-
43-
result[camel_key] = value
34+
for dictionary in dict_list:
35+
result = {**result, **self._convert_dictionary(dictionary, list(result.keys()))}
4436

4537
return result
4638

@@ -84,6 +76,31 @@ def _convert_lower_camel_case(param_name):
8476

8577
return f"{first_word}{word_string}"
8678

79+
def _convert_dictionary(self, dictionary, existing_keys):
80+
"""
81+
Iteratively process a dictionary to convert it to expected JSON
82+
83+
:raises KeyError: In case there is a duplicate key name in the dictionary.
84+
:returns: A single dictionary of lowerCamelCase key/value pairs.
85+
"""
86+
87+
result = {}
88+
89+
for key, value in dictionary.items():
90+
camel_key = self._convert_lower_camel_case(key)
91+
92+
if value is None:
93+
continue
94+
if camel_key in existing_keys:
95+
raise KeyError(f"Attempted to insert duplicate key '{camel_key}'")
96+
if isinstance(value, dict):
97+
value = self._convert_dictionary(value, [])
98+
99+
existing_keys.append(camel_key)
100+
result[camel_key] = value
101+
102+
return result
103+
87104
def _get_schema(self, subtype=None):
88105
"""
89106
Get the schema for the current object type.
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Lacework ReportDefinitions API wrapper.
4+
"""
5+
6+
from laceworksdk.api.crud_endpoint import CrudEndpoint
7+
8+
9+
class ReportDefinitionsAPI(CrudEndpoint):
10+
11+
def __init__(self, session):
12+
"""
13+
Initializes the ReportDefinitionsAPI object.
14+
15+
:param session: An instance of the HttpSession class
16+
17+
:return ReportDefinitionsAPI object.
18+
"""
19+
20+
super().__init__(session, "ReportDefinitions")
21+
22+
def create(self,
23+
report_name,
24+
report_type,
25+
sub_report_type,
26+
report_definition,
27+
props,
28+
alert_channels,
29+
distribution_type,
30+
frequency,
31+
**request_params):
32+
"""
33+
A method to create a new ReportDefinitions object.
34+
35+
:param report_name: A string representing the name of the report definition.
36+
:param report_type: A string representing the type of the report definition.
37+
:param sub_report_name: A string representing the sub-type of the report definition.
38+
("AWS", "GCP", "Azure")
39+
:param report_definition: An object representing the the report definition.
40+
obj:
41+
:param sections: An array of objects representing the sections of the report definition.
42+
:param category: A string representing the section's category.
43+
:param title: A string representing the section's title.
44+
:param policies: An array of strings representing the section's policies.
45+
:param overrides: An array of objects representing the overrides of the report definition.
46+
:param title: A string representing the policy's title.
47+
:param policy: A string representing the policy ID.
48+
:param props: An object representing metadata about the report definition.
49+
obj:
50+
:param engine: A string representing the evaluation engine used for the report.
51+
:param integrations: An array of strings representing integrations (e.g. AWS Account IDs)
52+
:param resource_groups: An array of strings representing resource group IDs.
53+
:param alert_channels: An array of strings representing the alert channels for report distribution.
54+
:param distribution_type: A string representing the report format.
55+
("csv", "html", "pdf")
56+
:param frequency: A string representing the frequency of report distribution.
57+
("daily", "weekly")
58+
:param request_params: Additional request parameters.
59+
(provides support for parameters that may be added in the future)
60+
61+
:return response json
62+
"""
63+
64+
return super().create(
65+
report_name=report_name,
66+
report_type=report_type,
67+
sub_report_type=sub_report_type,
68+
report_definition=report_definition,
69+
props=props,
70+
alert_channels=alert_channels,
71+
distribution_type=distribution_type,
72+
frequency=frequency,
73+
**request_params
74+
)
75+
76+
def get(self,
77+
id=None):
78+
"""
79+
A method to get ReportDefinitions objects.
80+
81+
:param id: A string representing the object ID.
82+
83+
:return response json
84+
"""
85+
86+
return super().get(id=id)
87+
88+
def get_by_id(self,
89+
id):
90+
"""
91+
A method to get a ReportDefinitions object by ID.
92+
93+
:param id: A string representing the object ID.
94+
95+
:return response json
96+
"""
97+
98+
return self.get(id=id)
99+
100+
def search(self, **request_params):
101+
"""
102+
A method to 'pass' when attempting to search ReportDefinitions objects.
103+
104+
Search functionality is not yet implemented for Alert Profiles.
105+
"""
106+
pass
107+
108+
def update(self,
109+
id,
110+
report_name,
111+
report_type,
112+
sub_report_type,
113+
report_definition,
114+
props=None,
115+
alert_channels=None,
116+
distribution_type=None,
117+
frequency=None,
118+
update_type=None,
119+
**request_params):
120+
"""
121+
A method to update an ReportDefinitions object.
122+
123+
:param id: A string representing the object ID.
124+
:param report_name: A string representing the name of the report definition.
125+
:param report_type: A string representing the type of the report definition.
126+
:param sub_report_name: A string representing the sub-type of the report definition.
127+
("AWS", "GCP", "Azure")
128+
:param report_definition: An object representing the the report definition.
129+
obj:
130+
:param sections: An array of objects representing the sections of the report definition.
131+
:param category: A string representing the section's category.
132+
:param title: A string representing the section's title.
133+
:param policies: An array of strings representing the section's policies.
134+
:param overrides: An array of objects representing the overrides of the report definition.
135+
:param title: A string representing the policy's title.
136+
:param policy: A string representing the policy ID.
137+
:param props: An object representing metadata about the report definition.
138+
obj:
139+
:param engine: A string representing the evaluation engine used for the report.
140+
:param integrations: An array of strings representing integrations (e.g. AWS Account IDs)
141+
:param resource_groups: An array of strings representing resource group IDs.
142+
:param alert_channels: An array of strings representing the alert channels for report distribution.
143+
:param distribution_type: A string representing the report format.
144+
("csv", "html", "pdf")
145+
:param frequency: A string representing the frequency of report distribution.
146+
("daily", "weekly")
147+
:param update_type: A string representing the type of update for the report definition.
148+
("Update", "Revert")
149+
:param request_params: Additional request parameters.
150+
(provides support for parameters that may be added in the future)
151+
152+
:return response json
153+
"""
154+
155+
json = self.build_dict_from_items(
156+
report_name=report_name,
157+
report_type=report_type,
158+
sub_report_type=sub_report_type,
159+
report_definition=report_definition,
160+
props=props,
161+
alert_channels=alert_channels,
162+
distribution_type=distribution_type,
163+
frequency=frequency,
164+
update_type=update_type,
165+
**request_params
166+
)
167+
168+
response = self._session.put(self.build_url(id=id), json=json)
169+
170+
return response.json()
171+
172+
def delete(self,
173+
id):
174+
"""
175+
A method to delete a ReportDefinitions object.
176+
177+
:param guid: A string representing the object ID.
178+
179+
:return response json
180+
"""
181+
182+
return super().delete(id=id)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Test suite for the community-developed Python SDK for interacting with Lacework APIs.
4+
"""
5+
6+
import random
7+
8+
import pytest
9+
10+
from laceworksdk.api.v2.report_definitions import ReportDefinitionsAPI
11+
from tests.api.test_crud_endpoint import CrudEndpoint
12+
13+
14+
# Tests
15+
16+
@pytest.fixture(scope="module")
17+
def api_object(api):
18+
return api.report_definitions
19+
20+
21+
@pytest.fixture(scope="module")
22+
def aws_account(api):
23+
cloud_accounts = api.cloud_accounts.get_by_type("AwsCfg")
24+
25+
if len(cloud_accounts["data"]):
26+
aws_role = random.choice(cloud_accounts["data"])["data"]["crossAccountCredentials"]["roleArn"]
27+
aws_account = aws_role.split(":")[4]
28+
return aws_account
29+
30+
31+
@pytest.fixture(scope="module")
32+
def api_object_create_body(random_text, aws_resource_group_guid, email_alert_channel_guid):
33+
return {
34+
"report_name": f"Test_{random_text}_Report",
35+
"report_type": "COMPLIANCE",
36+
"sub_report_type": "AWS",
37+
"report_definition": {
38+
"sections": [
39+
{
40+
"category": "1",
41+
"title": "Critical policies collection",
42+
"policies": [
43+
"AWS_CIS_1_1"
44+
]
45+
}
46+
],
47+
"overrides": [
48+
{
49+
"title": "Non `root` user authentication violation",
50+
"policy": "AWS_CIS_1_1"
51+
}
52+
]
53+
},
54+
"props": {
55+
"resourceGroups": [
56+
aws_resource_group_guid
57+
]
58+
},
59+
"alert_channels": [
60+
email_alert_channel_guid
61+
],
62+
"distribution_type": "pdf",
63+
"frequency": "daily"
64+
}
65+
66+
67+
@pytest.fixture(scope="module")
68+
def api_object_update_body(random_text):
69+
return {
70+
"report_name": f"Test_{random_text}_Report",
71+
"report_type": "COMPLIANCE",
72+
"sub_report_type": "AWS",
73+
"report_definition": {
74+
"sections": [
75+
{
76+
"category": "1",
77+
"title": "Critical policies collection",
78+
"policies": [
79+
"AWS_CIS_1_1"
80+
]
81+
}
82+
],
83+
"overrides": [
84+
{
85+
"title": "Non `root` user authentication violation",
86+
"policy": "AWS_CIS_1_1"
87+
}
88+
]
89+
},
90+
"frequency": "weekly",
91+
"update_type": "Update"
92+
}
93+
94+
95+
@pytest.mark.flaky_test
96+
class TestAlertProfiles(CrudEndpoint):
97+
98+
OBJECT_ID_NAME = "reportDefinitionGuid"
99+
OBJECT_TYPE = ReportDefinitionsAPI
100+
OBJECT_PARAM_EXCEPTIONS = ["alerts"]
101+
102+
def test_api_search(self):
103+
"""
104+
Search is unavailable for this endpoint.
105+
"""
106+
pass
107+
108+
def test_api_get_by_id(self, api_object):
109+
self._get_object_classifier_test(api_object, "id", self.OBJECT_ID_NAME)

0 commit comments

Comments
 (0)