regscale-cli 6.20.10.0__py3-none-any.whl → 6.21.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +12 -5
- regscale/core/app/internal/set_permissions.py +58 -27
- regscale/integrations/commercial/nessus/scanner.py +2 -0
- regscale/integrations/commercial/sonarcloud.py +35 -36
- regscale/integrations/commercial/synqly/ticketing.py +51 -0
- regscale/integrations/integration_override.py +15 -6
- regscale/integrations/scanner_integration.py +163 -35
- regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
- regscale/models/integration_models/aqua.py +92 -78
- regscale/models/integration_models/cisa_kev_data.json +47 -4
- regscale/models/integration_models/defenderimport.py +64 -59
- regscale/models/integration_models/ecr_models/ecr.py +100 -147
- regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
- regscale/models/integration_models/ibm.py +29 -47
- regscale/models/integration_models/nexpose.py +156 -68
- regscale/models/integration_models/prisma.py +46 -66
- regscale/models/integration_models/qualys.py +99 -93
- regscale/models/integration_models/snyk.py +229 -158
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/veracode.py +15 -20
- regscale/models/integration_models/xray.py +276 -82
- regscale/models/regscale_models/control_implementation.py +14 -12
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/rbac.py +22 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/RECORD +37 -36
- tests/fixtures/test_fixture.py +58 -2
- tests/regscale/core/test_app.py +5 -3
- tests/regscale/integrations/test_integration_mapping.py +522 -40
- tests/regscale/integrations/test_issue_due_date.py +1 -1
- tests/regscale/integrations/test_update_finding_dates.py +336 -0
- tests/regscale/models/test_asset.py +406 -50
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.0.0.dist-info}/top_level.txt +0 -0
regscale/_version.py
CHANGED
regscale/core/app/application.py
CHANGED
|
@@ -245,6 +245,7 @@ class Application(metaclass=Singleton):
|
|
|
245
245
|
"snowPassword": "<snowPassword>",
|
|
246
246
|
"snowUrl": "<mySnowUrl>",
|
|
247
247
|
"snowUserName": "<snowUserName>",
|
|
248
|
+
"sonarUrl": "https://sonarcloud.io",
|
|
248
249
|
"sonarToken": "<mySonarToken>",
|
|
249
250
|
"tenableAccessKey": "<tenableAccessKeyGoesHere>",
|
|
250
251
|
"tenableSecretKey": "<tenableSecretKeyGoesHere>",
|
|
@@ -295,7 +296,10 @@ class Application(metaclass=Singleton):
|
|
|
295
296
|
self.running_in_airflow = os.getenv("REGSCALE_AIRFLOW") == "true"
|
|
296
297
|
if isinstance(config, str):
|
|
297
298
|
config = self._read_config_from_str(config)
|
|
298
|
-
|
|
299
|
+
if self.running_in_airflow:
|
|
300
|
+
self.config = self._fetch_config_from_regscale(config)
|
|
301
|
+
else:
|
|
302
|
+
self.config = self._gen_config(config)
|
|
299
303
|
self.os = platform.system()
|
|
300
304
|
self.input_host = ""
|
|
301
305
|
# Ensure maxThreads is an integer for ThreadManager
|
|
@@ -536,10 +540,6 @@ class Application(metaclass=Singleton):
|
|
|
536
540
|
self.logger.debug("Successfully retrieved config from Click context.")
|
|
537
541
|
return click_config
|
|
538
542
|
|
|
539
|
-
if self.running_in_airflow:
|
|
540
|
-
if airflow_config := self._get_airflow_config(config):
|
|
541
|
-
self.logger.debug("Successfully retrieved config from Airflow.")
|
|
542
|
-
return airflow_config
|
|
543
543
|
try:
|
|
544
544
|
if config and self.local_config:
|
|
545
545
|
self.logger.debug(f"Config provided as :\n{type(config)}")
|
|
@@ -572,6 +572,13 @@ class Application(metaclass=Singleton):
|
|
|
572
572
|
return config
|
|
573
573
|
|
|
574
574
|
def _get_airflow_config(self, config: Optional[Union[dict, str]] = None) -> Optional[dict]:
|
|
575
|
+
"""
|
|
576
|
+
Get config from Airflow DAG config, or from the environment variables if not provided.
|
|
577
|
+
|
|
578
|
+
:param Optional[Union[dict, str]] config: Configuration dictionary, defaults to None
|
|
579
|
+
:return: Configuration dictionary
|
|
580
|
+
:rtype: Optional[dict]
|
|
581
|
+
"""
|
|
575
582
|
if config:
|
|
576
583
|
self.logger.debug(f"Received config from Airflow as: {type(config)}")
|
|
577
584
|
# check to see if config is a string because airflow can pass a string instead of a dict
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
import os
|
|
4
|
-
from
|
|
5
|
-
from openpyxl
|
|
6
|
-
from openpyxl.worksheet.worksheet import Worksheet
|
|
4
|
+
from rich.progress import track
|
|
5
|
+
from openpyxl import Workbook
|
|
7
6
|
from openpyxl.worksheet.datavalidation import DataValidation
|
|
8
|
-
from openpyxl import Workbook, load_workbook
|
|
9
7
|
from openpyxl.utils.dataframe import dataframe_to_rows
|
|
10
8
|
|
|
11
9
|
from regscale.core.app.logz import create_logger
|
|
@@ -118,38 +116,71 @@ def import_permissions_workbook(file: Path):
|
|
|
118
116
|
"""
|
|
119
117
|
# Read in the spreadsheet
|
|
120
118
|
records = get_records(file=file)
|
|
121
|
-
for
|
|
119
|
+
for index in track(
|
|
120
|
+
range(len(records)),
|
|
121
|
+
description="Processing rbacs...",
|
|
122
|
+
):
|
|
123
|
+
record = records[index]
|
|
124
|
+
# Check the records:
|
|
125
|
+
|
|
122
126
|
if not Group.get_object(record["group_id"]):
|
|
123
127
|
logger.error(f"Group {record['group_id']} doesn't exist in this instance. Skipping row")
|
|
124
128
|
continue
|
|
125
129
|
|
|
126
|
-
regscale_module_id = Modules.get_module_to_id(record["regscale_module"])
|
|
127
|
-
regscale_id = record["regscale_id"]
|
|
128
|
-
if record[READ_UPDATE] == "R":
|
|
129
|
-
permissions = 1
|
|
130
|
-
elif record[READ_UPDATE] == "RU":
|
|
131
|
-
permissions = 2
|
|
132
|
-
else:
|
|
133
|
-
permissions = 0
|
|
134
|
-
|
|
135
|
-
# Set the record to private
|
|
136
130
|
my_class = Modules.module_to_class(record["regscale_module"])
|
|
137
|
-
obj = my_class.get_object(regscale_id)
|
|
131
|
+
obj = my_class.get_object(record["regscale_id"])
|
|
138
132
|
if not obj:
|
|
139
|
-
logger.error(
|
|
133
|
+
logger.error(
|
|
134
|
+
f"RegScale {record['regscale_module']} record {record['regscale_id']} doesn't exist. Skipping row"
|
|
135
|
+
)
|
|
140
136
|
continue
|
|
141
137
|
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
138
|
+
# Set the permissions
|
|
139
|
+
set_permissions(record=record)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def set_permissions(record: dict):
|
|
143
|
+
"""
|
|
144
|
+
Sets the permissions for each record
|
|
145
|
+
|
|
146
|
+
:param dict record: permissions dictionary
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
if record[READ_UPDATE] == "R":
|
|
150
|
+
permissions = 1
|
|
151
|
+
elif record[READ_UPDATE] == "RU":
|
|
152
|
+
permissions = 2
|
|
153
|
+
else:
|
|
154
|
+
permissions = 0
|
|
155
|
+
|
|
156
|
+
# Add the permission
|
|
157
|
+
if not RBAC.add(
|
|
158
|
+
module_id=Modules.get_module_to_id(record["regscale_module"]),
|
|
159
|
+
parent_id=record["regscale_id"],
|
|
160
|
+
group_id=record["group_id"],
|
|
161
|
+
permission_type=permissions,
|
|
162
|
+
):
|
|
163
|
+
logger.warning(
|
|
164
|
+
f"Failed to set permissions for {record['regscale_module']} {record['regscale_id']} for group {record['group_id']}"
|
|
165
|
+
)
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
if not RBAC.public(
|
|
169
|
+
module_id=Modules.get_module_to_id(record["regscale_module"]),
|
|
170
|
+
parent_id=record["regscale_id"],
|
|
171
|
+
is_public=0 if record[PUBLIC_PRIVATE] == "private" else 1,
|
|
172
|
+
):
|
|
173
|
+
logger.warning(
|
|
174
|
+
f"Failed to set public/private for {record['regscale_module']} {record['regscale_id']} for group {record['group_id']}"
|
|
148
175
|
)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
if not RBAC.reset(
|
|
179
|
+
module_id=Modules.get_module_to_id(record["regscale_module"]),
|
|
180
|
+
parent_id=record["regscale_id"],
|
|
181
|
+
):
|
|
182
|
+
logger.warning(
|
|
183
|
+
f"Failed to proliferate permissions for {record['regscale_module']} {record['regscale_id']} for group {record['group_id']}"
|
|
153
184
|
)
|
|
154
185
|
|
|
155
186
|
|
|
@@ -83,6 +83,8 @@ class NessusIntegration(ScannerIntegration):
|
|
|
83
83
|
for file in iterate_files(file_collection):
|
|
84
84
|
content = read_file(file)
|
|
85
85
|
root = ET.fromstring(content)
|
|
86
|
+
if scan_dt := nfr.scan.scan_time_start(root):
|
|
87
|
+
self.scan_date = scan_dt.strftime("%Y-%m-%d")
|
|
86
88
|
for nessus_asset in nfr.scan.report_hosts(root):
|
|
87
89
|
asset_name = nfr.host.report_host_name(nessus_asset)
|
|
88
90
|
for nessus_vulnerability in root.iterfind(f"./Report/ReportHost[@name='{asset_name}']/ReportItem"):
|
|
@@ -2,100 +2,93 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""RegScale SonarCloud Integration"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import logging
|
|
6
6
|
import math
|
|
7
|
-
import sys
|
|
8
7
|
from typing import Optional
|
|
8
|
+
from urllib.parse import urljoin
|
|
9
9
|
|
|
10
10
|
import click
|
|
11
11
|
import requests # type: ignore
|
|
12
12
|
|
|
13
13
|
from regscale.core.app.api import Api
|
|
14
14
|
from regscale.core.app.application import Application
|
|
15
|
-
from regscale.core.app.logz import create_logger
|
|
16
15
|
from regscale.core.app.utils.app_utils import (
|
|
17
|
-
get_current_datetime,
|
|
18
16
|
days_between,
|
|
17
|
+
error_and_exit,
|
|
18
|
+
get_current_datetime,
|
|
19
19
|
)
|
|
20
20
|
from regscale.models import regscale_id, regscale_module
|
|
21
21
|
from regscale.models.regscale_models.assessment import Assessment
|
|
22
22
|
from regscale.models.regscale_models.issue import Issue
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
logger = create_logger()
|
|
24
|
+
logger = logging.getLogger("regscale")
|
|
26
25
|
|
|
27
26
|
|
|
28
|
-
def get_sonarcloud_results(config: dict) -> list[list[dict]]:
|
|
27
|
+
def get_sonarcloud_results(config: dict, branch: Optional[str] = None) -> list[list[dict]]:
|
|
29
28
|
"""
|
|
30
29
|
Retrieve Sonarcloud Results from the Sonarcloud.io API
|
|
30
|
+
|
|
31
31
|
:param dict config: RegScale CLI configuration
|
|
32
|
+
:param Optional[str] branch: Branch name to filter results, defaults to None
|
|
32
33
|
:return: json response data from API GET request
|
|
33
34
|
:rtype: list[list[dict]]
|
|
34
35
|
"""
|
|
36
|
+
# create an empty list to hold multiple pages of data
|
|
37
|
+
complete = []
|
|
35
38
|
# api endpoint
|
|
36
|
-
url = "
|
|
39
|
+
url = urljoin(config["sonarUrl"], "/api/issues/search")
|
|
37
40
|
# SONAR_TOKEN from Sonarcloud
|
|
38
41
|
token = config["sonarToken"]
|
|
39
42
|
# arguments to pass to the API call
|
|
40
|
-
|
|
41
|
-
"organization": "regscale",
|
|
42
|
-
"projects": "RegScale_regscale",
|
|
43
|
-
"branch": "main",
|
|
44
|
-
"projectKey": "RegScale_regscale",
|
|
43
|
+
params = {
|
|
45
44
|
"statuses": "OPEN, CONFIRMED, REOPENED",
|
|
46
|
-
"createdInLast": "1m",
|
|
47
45
|
"ps": 500,
|
|
48
46
|
}
|
|
47
|
+
if branch:
|
|
48
|
+
params["branch"] = branch
|
|
49
49
|
# GET request pulls in data to check results size
|
|
50
|
-
r = requests.get(url, auth=(str(token), ""), params=
|
|
50
|
+
r = requests.get(url, auth=(str(token), ""), params=params)
|
|
51
51
|
# if the status code does not equal 200
|
|
52
52
|
if r and not r.ok:
|
|
53
53
|
# exit the script gracefully
|
|
54
|
-
|
|
54
|
+
error_and_exit(f"Sonarcloud API call failed please check the configuration\n{r.status_code}: {r.text}")
|
|
55
55
|
# pull in response data to a dictionary
|
|
56
56
|
data = r.json()
|
|
57
57
|
# find the total results number
|
|
58
58
|
total = data["paging"]["total"]
|
|
59
|
+
complete.extend(data.get("issues", []))
|
|
60
|
+
logger.info(f"Found {total} issue(s) from SonarCloud/Qube.")
|
|
59
61
|
# find the number of results in each result page
|
|
60
62
|
size = data["paging"]["pageSize"]
|
|
61
63
|
# calculate the number of pages to iterate through sequentially
|
|
62
64
|
pages = math.ceil(total / size)
|
|
63
|
-
# create an empty list to hold multiple pages of data
|
|
64
|
-
complete = []
|
|
65
65
|
# loop through each page number
|
|
66
66
|
for i in range(1, pages + 1, 1):
|
|
67
67
|
# parameters to pass to the API call
|
|
68
|
-
|
|
69
|
-
"organization": "regscale",
|
|
70
|
-
"projects": "RegScale_regscale",
|
|
71
|
-
"branch": "main",
|
|
72
|
-
"projectKey": "RegScale_regscale",
|
|
73
|
-
"statuses": "OPEN, CONFIRMED, REOPENED",
|
|
74
|
-
"createdInLast": "1m",
|
|
75
|
-
"ps": 500,
|
|
76
|
-
"p": f"{i}",
|
|
77
|
-
}
|
|
68
|
+
params["p"] = str(i)
|
|
78
69
|
# for each page make a GET request to pull in the data
|
|
79
|
-
r = requests.get(url, auth=(str(token), ""), params=
|
|
70
|
+
r = requests.get(url, auth=(str(token), ""), params=params)
|
|
80
71
|
# pull in response data to a dictionary
|
|
81
72
|
data = r.json()
|
|
82
73
|
# extract only the issues from the data
|
|
83
74
|
issues = data["issues"]
|
|
84
75
|
# add each page to the total results page
|
|
85
|
-
complete.
|
|
76
|
+
complete.extend(issues)
|
|
86
77
|
# return the list of json response objects for use
|
|
78
|
+
logger.info(f"Retrieved {len(complete)}/{total} issue(s) from SonarCloud/Qube.")
|
|
87
79
|
return complete
|
|
88
80
|
|
|
89
81
|
|
|
90
|
-
def build_data(api: Api) -> list[dict]:
|
|
82
|
+
def build_data(api: Api, branch: Optional[str] = None) -> list[dict]:
|
|
91
83
|
"""
|
|
92
84
|
Build vulnerability alert data list
|
|
93
85
|
:param Api api: API object
|
|
86
|
+
:param Optional[str] branch: Branch name to filter results, defaults to None
|
|
94
87
|
:return: vulnerability data list
|
|
95
88
|
:rtype: list[dict]
|
|
96
89
|
"""
|
|
97
90
|
# execute GET request
|
|
98
|
-
data = get_sonarcloud_results(config=api.config)
|
|
91
|
+
data = get_sonarcloud_results(config=api.config, branch=branch)
|
|
99
92
|
# create empty list to hold json response dicts
|
|
100
93
|
vulnerability_data_list = []
|
|
101
94
|
# loop through the lists in API response data
|
|
@@ -196,11 +189,14 @@ def create_alert_assessment(
|
|
|
196
189
|
return None
|
|
197
190
|
|
|
198
191
|
|
|
199
|
-
def create_alert_issues(
|
|
192
|
+
def create_alert_issues(
|
|
193
|
+
parent_id: Optional[int] = None, parent_module: Optional[str] = None, branch: Optional[str] = None
|
|
194
|
+
) -> None:
|
|
200
195
|
"""
|
|
201
196
|
Create child issues from the alert assessment
|
|
202
197
|
:param Optional[int] parent_id: Parent ID record to associate the assessment to, defaults to None
|
|
203
198
|
:param Optional[str] parent_module: Parent module to associate the assessment to, defaults to None
|
|
199
|
+
:param Optional[str] branch: Branch name to filter results, defaults to None
|
|
204
200
|
:rtype: None
|
|
205
201
|
"""
|
|
206
202
|
# set environment and application configuration
|
|
@@ -210,7 +206,7 @@ def create_alert_issues(parent_id: Optional[int] = None, parent_module: Optional
|
|
|
210
206
|
assessment_id = create_alert_assessment(api=api, parent_id=parent_id, parent_module=parent_module)
|
|
211
207
|
|
|
212
208
|
# create vulnerability data list
|
|
213
|
-
vuln_data_list = build_data(api)
|
|
209
|
+
vuln_data_list = build_data(api, branch)
|
|
214
210
|
# loop through each vulnerability alert in the list
|
|
215
211
|
for vulnerability in vuln_data_list:
|
|
216
212
|
# create issue model
|
|
@@ -253,8 +249,11 @@ def sonarcloud() -> None:
|
|
|
253
249
|
@sonarcloud.command(name="sync_alerts")
|
|
254
250
|
@regscale_id(required=False, default=None)
|
|
255
251
|
@regscale_module(required=False, default=None)
|
|
256
|
-
|
|
252
|
+
@click.option("--branch", help="Branch name to filter results, defaults to None")
|
|
253
|
+
def create_alerts(
|
|
254
|
+
regscale_id: Optional[int] = None, regscale_module: Optional[str] = None, branch: Optional[str] = None
|
|
255
|
+
) -> None:
|
|
257
256
|
"""
|
|
258
257
|
Create a child assessment and child issues in RegScale from SonarCloud alerts.
|
|
259
258
|
"""
|
|
260
|
-
create_alert_issues(regscale_id, regscale_module)
|
|
259
|
+
create_alert_issues(regscale_id, regscale_module, branch)
|
|
@@ -100,6 +100,57 @@ def sync_jira(
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
|
|
103
|
+
@ticketing.command(name="sync_jira_service_management")
|
|
104
|
+
@regscale_id()
|
|
105
|
+
@regscale_module()
|
|
106
|
+
@click.option(
|
|
107
|
+
"--project",
|
|
108
|
+
type=click.STRING,
|
|
109
|
+
help="jira_service_management project",
|
|
110
|
+
required=True,
|
|
111
|
+
prompt="jira_service_management project",
|
|
112
|
+
)
|
|
113
|
+
@click.option(
|
|
114
|
+
"--default_issue_type",
|
|
115
|
+
type=click.STRING,
|
|
116
|
+
help="Default issue type when creating tickets.",
|
|
117
|
+
required=False,
|
|
118
|
+
)
|
|
119
|
+
@click.option(
|
|
120
|
+
"--default_project",
|
|
121
|
+
type=click.STRING,
|
|
122
|
+
help="Default project when listing, creating, or editing tickets.",
|
|
123
|
+
required=False,
|
|
124
|
+
)
|
|
125
|
+
@click.option(
|
|
126
|
+
"--sync_attachments",
|
|
127
|
+
type=click.BOOL,
|
|
128
|
+
help="Whether to sync attachments between Jira Service Management and RegScale",
|
|
129
|
+
required=False,
|
|
130
|
+
default=True,
|
|
131
|
+
)
|
|
132
|
+
def sync_jira_service_management(
|
|
133
|
+
regscale_id: int,
|
|
134
|
+
regscale_module: str,
|
|
135
|
+
project: str,
|
|
136
|
+
default_issue_type: str,
|
|
137
|
+
default_project: str,
|
|
138
|
+
sync_attachments: bool,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""Sync Ticketing data between Jira Service Management and RegScale."""
|
|
141
|
+
from regscale.models.integration_models.synqly_models.connectors import Ticketing
|
|
142
|
+
|
|
143
|
+
ticketing_jira_service_management = Ticketing("jira_service_management")
|
|
144
|
+
ticketing_jira_service_management.run_sync(
|
|
145
|
+
regscale_id=regscale_id,
|
|
146
|
+
regscale_module=regscale_module,
|
|
147
|
+
project=project,
|
|
148
|
+
default_issue_type=default_issue_type,
|
|
149
|
+
default_project=default_project,
|
|
150
|
+
sync_attachments=sync_attachments,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
103
154
|
@ticketing.command(name="sync_pagerduty")
|
|
104
155
|
@regscale_id()
|
|
105
156
|
@regscale_module()
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
A simple singleton class that loads custom integration mappings, if available
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from regscale.core.app.application import Application
|
|
6
|
-
|
|
7
5
|
# pylint: disable=C0415
|
|
8
6
|
|
|
9
7
|
|
|
@@ -74,9 +72,12 @@ class IntegrationOverride:
|
|
|
74
72
|
:return: The mapped field name
|
|
75
73
|
:rtype: Optional[str]
|
|
76
74
|
"""
|
|
77
|
-
if integration and self.mapping_exists(integration, field_name):
|
|
75
|
+
if integration and field_name and self.mapping_exists(integration, field_name):
|
|
78
76
|
integration_map = self.mapping.get(integration.lower(), {})
|
|
79
|
-
|
|
77
|
+
# Find the actual key that matches case-insensitively
|
|
78
|
+
for key in integration_map.keys():
|
|
79
|
+
if key.lower() == field_name.lower():
|
|
80
|
+
return integration_map.get(key)
|
|
80
81
|
return None
|
|
81
82
|
|
|
82
83
|
def mapping_exists(self, integration: str, field_name: str) -> bool:
|
|
@@ -88,8 +89,16 @@ class IntegrationOverride:
|
|
|
88
89
|
:return: Whether the mapping exists
|
|
89
90
|
:rtype: bool
|
|
90
91
|
"""
|
|
92
|
+
if not integration or not field_name:
|
|
93
|
+
return False
|
|
91
94
|
the_map = self.mapping.get(integration.lower())
|
|
92
|
-
|
|
95
|
+
if not the_map:
|
|
96
|
+
return False
|
|
97
|
+
# Find the actual key that matches case-insensitively
|
|
98
|
+
for key in the_map.keys():
|
|
99
|
+
if key.lower() == field_name.lower():
|
|
100
|
+
return the_map.get(key) != "default"
|
|
101
|
+
return False
|
|
93
102
|
|
|
94
103
|
def field_map_validation(self, obj: Any, model_type: str) -> Optional[str]:
|
|
95
104
|
"""
|
|
@@ -131,7 +140,7 @@ class IntegrationOverride:
|
|
|
131
140
|
},
|
|
132
141
|
}
|
|
133
142
|
# The type an associated fields we are able to override. Limited for now.
|
|
134
|
-
supported_fields = {"asset": {"ipAddress", "name", "fqdn", "dns"}}
|
|
143
|
+
supported_fields = {"asset": {"ipAddress", "name", "fqdn", "dns"}, "issue": {"dateFirstDetected"}}
|
|
135
144
|
if regscale_field not in supported_fields.get(model_type.lower(), set()):
|
|
136
145
|
return match
|
|
137
146
|
|