regscale-cli 6.16.3.0__py3-none-any.whl → 6.16.4.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/__init__.py +1 -1
- regscale/core/app/internal/control_editor.py +26 -2
- regscale/core/app/internal/model_editor.py +39 -26
- regscale/integrations/commercial/grype/scanner.py +37 -29
- regscale/integrations/commercial/opentext/commands.py +2 -0
- regscale/integrations/commercial/opentext/scanner.py +45 -31
- regscale/integrations/commercial/qualys.py +3 -1
- regscale/integrations/commercial/sicura/commands.py +9 -14
- regscale/integrations/commercial/tenablev2/click.py +25 -13
- regscale/integrations/commercial/tenablev2/scanner.py +12 -3
- regscale/integrations/commercial/trivy/scanner.py +14 -6
- regscale/integrations/commercial/wizv2/click.py +15 -37
- regscale/integrations/jsonl_scanner_integration.py +120 -16
- regscale/integrations/public/fedramp/click.py +8 -8
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +499 -106
- regscale/integrations/public/fedramp/ssp_logger.py +2 -9
- regscale/integrations/scanner_integration.py +14 -9
- regscale/models/integration_models/cisa_kev_data.json +39 -8
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/tenable_models/integration.py +23 -3
- regscale/models/regscale_models/control_implementation.py +18 -0
- regscale/models/regscale_models/control_objective.py +2 -1
- regscale/models/regscale_models/facility.py +10 -26
- regscale/models/regscale_models/functional_roles.py +38 -0
- regscale/models/regscale_models/issue.py +3 -1
- regscale/models/regscale_models/parameter.py +21 -3
- regscale/models/regscale_models/profile.py +22 -0
- regscale/models/regscale_models/profile_mapping.py +48 -3
- regscale/models/regscale_models/regscale_model.py +2 -0
- regscale/models/regscale_models/risk.py +38 -30
- regscale/models/regscale_models/security_plan.py +1 -0
- regscale/models/regscale_models/supply_chain.py +1 -1
- regscale/models/regscale_models/user.py +16 -2
- regscale/utils/threading/__init__.py +1 -0
- regscale/utils/threading/threadsafe_list.py +10 -0
- regscale/utils/threading/threadsafe_set.py +116 -0
- {regscale_cli-6.16.3.0.dist-info → regscale_cli-6.16.4.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.16.3.0.dist-info → regscale_cli-6.16.4.0.dist-info}/RECORD +42 -40
- {regscale_cli-6.16.3.0.dist-info → regscale_cli-6.16.4.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.16.3.0.dist-info → regscale_cli-6.16.4.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.16.3.0.dist-info → regscale_cli-6.16.4.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.16.3.0.dist-info → regscale_cli-6.16.4.0.dist-info}/top_level.txt +0 -0
|
@@ -9,7 +9,12 @@ from typing import Any, Iterator, List, Optional, Tuple
|
|
|
9
9
|
from regscale.core.app.utils.app_utils import epoch_to_datetime
|
|
10
10
|
from regscale.integrations.commercial.tenablev2.utils import get_filtered_severities
|
|
11
11
|
from regscale.integrations.integration_override import IntegrationOverride
|
|
12
|
-
from regscale.integrations.scanner_integration import
|
|
12
|
+
from regscale.integrations.scanner_integration import (
|
|
13
|
+
IntegrationAsset,
|
|
14
|
+
IntegrationFinding,
|
|
15
|
+
ScannerIntegration,
|
|
16
|
+
issue_due_date,
|
|
17
|
+
)
|
|
13
18
|
from regscale.models import regscale_models
|
|
14
19
|
from regscale.models.integration_models.tenable_models.models import TenableAsset
|
|
15
20
|
|
|
@@ -32,6 +37,16 @@ class SCIntegration(ScannerIntegration):
|
|
|
32
37
|
title = "Tenable SC"
|
|
33
38
|
asset_identifier_field = "tenableId"
|
|
34
39
|
|
|
40
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
41
|
+
"""
|
|
42
|
+
Initializes the SCIntegration class
|
|
43
|
+
|
|
44
|
+
:param Tuple args: Additional arguments
|
|
45
|
+
:param dict kwargs: Additional keyword arguments
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(*args, **kwargs)
|
|
48
|
+
self.scan_date = kwargs.get("scan_date")
|
|
49
|
+
|
|
35
50
|
def fetch_assets(self, *args: Any, **kwargs: Any) -> Iterator[IntegrationAsset]:
|
|
36
51
|
"""
|
|
37
52
|
Fetches assets from SCIntegration
|
|
@@ -138,6 +153,7 @@ class SCIntegration(ScannerIntegration):
|
|
|
138
153
|
fixed_versions = re.findall(r"Fixed version\s*:\s*(.+)", vuln.pluginText)
|
|
139
154
|
fixed_versions_str = ", ".join(fixed_versions)
|
|
140
155
|
|
|
156
|
+
first_seen = epoch_to_datetime(vuln.firstSeen) if vuln.firstSeen else self.scan_date
|
|
141
157
|
return IntegrationFinding(
|
|
142
158
|
control_labels=[], # Add an empty list for control_labels
|
|
143
159
|
category="Tenable SC Vulnerability", # Add a default category
|
|
@@ -148,9 +164,9 @@ class SCIntegration(ScannerIntegration):
|
|
|
148
164
|
status=regscale_models.IssueStatus.Open, # Findings of > Low are considered as FAIL
|
|
149
165
|
asset_identifier=asset_identifier,
|
|
150
166
|
external_id=vuln.pluginID, # Weakness Source Identifier
|
|
151
|
-
first_seen=
|
|
167
|
+
first_seen=first_seen,
|
|
152
168
|
last_seen=epoch_to_datetime(vuln.lastSeen),
|
|
153
|
-
date_created=
|
|
169
|
+
date_created=first_seen,
|
|
154
170
|
date_last_updated=epoch_to_datetime(vuln.lastSeen),
|
|
155
171
|
recommendation_for_mitigation=vuln.solution,
|
|
156
172
|
cve=cve,
|
|
@@ -175,6 +191,10 @@ class SCIntegration(ScannerIntegration):
|
|
|
175
191
|
installed_versions=installed_versions_str,
|
|
176
192
|
fixed_versions=fixed_versions_str,
|
|
177
193
|
fix_status="",
|
|
194
|
+
scan_date=self.scan_date,
|
|
195
|
+
due_date=issue_due_date(
|
|
196
|
+
severity=severity, created_date=first_seen, title="tenable", config=self.app.config
|
|
197
|
+
),
|
|
178
198
|
)
|
|
179
199
|
|
|
180
200
|
def get_cvss_scores(self, vuln: TenableAsset) -> dict:
|
|
@@ -212,8 +212,26 @@ class ControlImplementation(RegScaleModel):
|
|
|
212
212
|
get_all_asset_controls_by_component="/api/{model_slug}/getAllAssetControlsByComponent/{int_id}",
|
|
213
213
|
drilldown_asset_controls_by_component="/api/{model_slug}/drilldownAssetControlsByComponent/{component_id}/{str_field}/{str_value}", # noqa: E501
|
|
214
214
|
get_control_context="/api/{model_slug}/getControlContext/{int_control_id}/{int_parent_id}/{str_module}",
|
|
215
|
+
get_control_with_all_child_details="/api/{model_slug}/getControlWithAllChildDetails/{intId}",
|
|
215
216
|
)
|
|
216
217
|
|
|
218
|
+
@classmethod
|
|
219
|
+
def get_with_child_details(cls, implementation_id: int) -> Optional[list[dict]]:
|
|
220
|
+
"""
|
|
221
|
+
Get control implementation with all child details.
|
|
222
|
+
|
|
223
|
+
Retrieve a control implementation with all supporting data in a single call.
|
|
224
|
+
|
|
225
|
+
:param int implementation_id: The ID of the control implementation
|
|
226
|
+
:return: A list of control implementation details or None
|
|
227
|
+
:rtype: Optional[list[dict]]
|
|
228
|
+
"""
|
|
229
|
+
endpoint = cls.get_endpoint("get_control_with_all_child_details").format(intId=implementation_id)
|
|
230
|
+
response = cls._get_api_handler().get(endpoint)
|
|
231
|
+
if response and response.ok:
|
|
232
|
+
return response.json()
|
|
233
|
+
return None
|
|
234
|
+
|
|
217
235
|
def find_by_unique(self, **kwargs: dict) -> Optional["ControlImplementation"]:
|
|
218
236
|
"""
|
|
219
237
|
Find an object by unique query.
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
from collections import defaultdict
|
|
5
5
|
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
from pydantic import
|
|
7
|
+
from pydantic import ConfigDict, Field
|
|
8
8
|
|
|
9
9
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
10
10
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
@@ -25,6 +25,7 @@ class ControlObjective(RegScaleModel):
|
|
|
25
25
|
otherId: str = "" # API does not return if None
|
|
26
26
|
archived: bool = False
|
|
27
27
|
securityControlId: int
|
|
28
|
+
parentObjectiveId: Optional[int] = None
|
|
28
29
|
dateCreated: str = Field(default_factory=get_current_datetime)
|
|
29
30
|
dateLastUpdated: str = Field(default_factory=get_current_datetime)
|
|
30
31
|
objectiveType: str = "objective"
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""Facility model for RegScale."""
|
|
2
2
|
|
|
3
|
-
import warnings
|
|
4
3
|
from typing import Optional
|
|
5
|
-
from urllib.parse import urljoin
|
|
6
4
|
|
|
7
|
-
from
|
|
8
|
-
|
|
5
|
+
from pydantic import ConfigDict
|
|
6
|
+
|
|
9
7
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
10
8
|
|
|
11
9
|
|
|
@@ -32,28 +30,14 @@ class Facility(RegScaleModel):
|
|
|
32
30
|
isPublic: bool = True
|
|
33
31
|
dateLastUpdated: Optional[str] = None
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
@staticmethod
|
|
34
|
+
def _get_additional_endpoints() -> dict:
|
|
35
|
+
"""
|
|
36
|
+
Get additional endpoints for the Facility model.
|
|
37
37
|
|
|
38
|
-
:
|
|
39
|
-
:
|
|
40
|
-
:rtype: Optional[dict]
|
|
38
|
+
:return: A dictionary of additional endpoints
|
|
39
|
+
:rtype: dict
|
|
41
40
|
"""
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
DeprecationWarning,
|
|
45
|
-
)
|
|
46
|
-
api = Api()
|
|
47
|
-
url = urljoin(app.config.get("domain", ""), "/api/facilities")
|
|
48
|
-
data = self.dict()
|
|
49
|
-
response = api.post(url, json=data)
|
|
50
|
-
return response.json() if response.ok else None
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
class Facilities(Facility):
|
|
54
|
-
def __init__(self, *args, **kwargs):
|
|
55
|
-
warnings.warn(
|
|
56
|
-
"The 'Facilities' class is deprecated, use 'Facility' instead",
|
|
57
|
-
DeprecationWarning,
|
|
41
|
+
return ConfigDict(
|
|
42
|
+
get_list="/api/{model_slug}/get_list",
|
|
58
43
|
)
|
|
59
|
-
super().__init__(*args, **kwargs)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from pydantic import ConfigDict
|
|
4
|
+
|
|
5
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FunctionalRole(RegScaleModel):
|
|
9
|
+
"""
|
|
10
|
+
Functional Role Model
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
_module_slug = "functionalroles"
|
|
14
|
+
|
|
15
|
+
id: int = 0
|
|
16
|
+
role: str
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def get_list(cls) -> List["FunctionalRole"]:
|
|
20
|
+
"""
|
|
21
|
+
Get list of Functional Roles
|
|
22
|
+
|
|
23
|
+
:return: list of Functional Roles
|
|
24
|
+
:rtype: List["FunctionalRole"]
|
|
25
|
+
"""
|
|
26
|
+
return cls._handle_list_response(cls._get_api_handler().get(cls.get_endpoint("get_list")))
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def _get_additional_endpoints() -> ConfigDict:
|
|
30
|
+
"""
|
|
31
|
+
Get additional endpoints for the FunctionalRole
|
|
32
|
+
|
|
33
|
+
:return: Additional endpoints for the FunctionalRole
|
|
34
|
+
:rtype: ConfigDict
|
|
35
|
+
"""
|
|
36
|
+
return ConfigDict( # type: ignore
|
|
37
|
+
get_list="/api/{model_slug}/getList",
|
|
38
|
+
)
|
|
@@ -788,7 +788,9 @@ class Issue(RegScaleModel):
|
|
|
788
788
|
take = 50
|
|
789
789
|
skip = 0
|
|
790
790
|
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
791
|
-
logger.info(
|
|
791
|
+
logger.info(
|
|
792
|
+
f"Fetching open issues for controls and for security plan {plan_id}...",
|
|
793
|
+
)
|
|
792
794
|
supports_multiple_controls: bool = cls.is_multiple_controls_supported()
|
|
793
795
|
|
|
794
796
|
# Define fields based on version
|
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
"""Regscale Model for Implementation Parameter in the application"""
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import List, Optional
|
|
7
7
|
|
|
8
|
-
from pydantic import ConfigDict
|
|
9
|
-
from pydantic import Field
|
|
8
|
+
from pydantic import ConfigDict, Field
|
|
10
9
|
|
|
11
10
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
12
11
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
@@ -85,3 +84,22 @@ class Parameter(RegScaleModel):
|
|
|
85
84
|
if response and response.ok:
|
|
86
85
|
return [Parameter(**param) for param in response.json()]
|
|
87
86
|
return []
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def merge_parameters(cls, implementation_id: int, security_control_id: int) -> List["Parameter"]:
|
|
90
|
+
"""
|
|
91
|
+
Get a list of parameters by implementation ID and security control ID.
|
|
92
|
+
|
|
93
|
+
:param int implementation_id: The ID of the controlImplementation
|
|
94
|
+
:param int security_control_id: The ID of the security control
|
|
95
|
+
:return: A list of parameters
|
|
96
|
+
:rtype: List["Parameter"]
|
|
97
|
+
"""
|
|
98
|
+
response = cls._get_api_handler().get(
|
|
99
|
+
endpoint=cls.get_endpoint("merge").format(
|
|
100
|
+
implementationID=implementation_id, securityControlID=security_control_id
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
if response and response.ok:
|
|
104
|
+
return [cls(**ci) for ci in response.json()]
|
|
105
|
+
return []
|
|
@@ -30,6 +30,28 @@ class Profile(RegScaleModel):
|
|
|
30
30
|
confidentiality: Optional[str] = None
|
|
31
31
|
integrity: Optional[str] = None
|
|
32
32
|
|
|
33
|
+
@classmethod
|
|
34
|
+
def apply_profile(cls, module_id: int, module_name: str, profile_id: int, is_public: bool) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Apply a profile to a module.
|
|
37
|
+
|
|
38
|
+
:param int module_id: The module ID
|
|
39
|
+
:param str module_name: The module name
|
|
40
|
+
:param int profile_id: The profile ID
|
|
41
|
+
:param bool is_public: Whether the profile is public
|
|
42
|
+
:return: True if the profile was applied
|
|
43
|
+
:rtype: bool
|
|
44
|
+
"""
|
|
45
|
+
endpoint = cls.get_endpoint("apply_profile").format(
|
|
46
|
+
model_slug=cls._module_slug,
|
|
47
|
+
moduleId=module_id,
|
|
48
|
+
moduleName=module_name,
|
|
49
|
+
profileId=profile_id,
|
|
50
|
+
isPublic=is_public,
|
|
51
|
+
)
|
|
52
|
+
response = cls._get_api_handler().post(endpoint)
|
|
53
|
+
return response.ok
|
|
54
|
+
|
|
33
55
|
@staticmethod
|
|
34
56
|
def _get_additional_endpoints() -> ConfigDict:
|
|
35
57
|
"""
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""Model for Profile Mapping in the application"""
|
|
4
|
-
|
|
4
|
+
import importlib
|
|
5
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
5
6
|
from urllib.parse import urljoin
|
|
6
7
|
|
|
7
8
|
from pydantic import ConfigDict, Field
|
|
@@ -11,6 +12,9 @@ from regscale.core.app.application import Application
|
|
|
11
12
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
12
13
|
from regscale.models.regscale_models.security_control import SecurityControl
|
|
13
14
|
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from regscale.models import ControlImplementation, ControlImplementationStatus
|
|
17
|
+
|
|
14
18
|
|
|
15
19
|
class ProfileMapping(RegScaleModel):
|
|
16
20
|
"""
|
|
@@ -33,6 +37,41 @@ class ProfileMapping(RegScaleModel):
|
|
|
33
37
|
isPublic: bool = True
|
|
34
38
|
lastUpdatedById: Optional[str] = None
|
|
35
39
|
|
|
40
|
+
def __init__(self, **data):
|
|
41
|
+
super().__init__(**data)
|
|
42
|
+
|
|
43
|
+
# Create a lazily loaded reference to avoid circular imports
|
|
44
|
+
def _get_control_implementation_classes(self):
|
|
45
|
+
"""
|
|
46
|
+
Get the control implementation classes in a lazy manner
|
|
47
|
+
|
|
48
|
+
:return: The control implementation classes
|
|
49
|
+
:rtype: Tuple[ControlImplementation, ControlImplementationStatus]
|
|
50
|
+
:raises ImportError: If the module cannot be imported
|
|
51
|
+
"""
|
|
52
|
+
module = importlib.import_module("regscale.models")
|
|
53
|
+
return (module.ControlImplementation, module.ControlImplementationStatus)
|
|
54
|
+
|
|
55
|
+
def to_implementation(self, security_plan_id: int):
|
|
56
|
+
"""
|
|
57
|
+
Convert a profile mapping to a control implementation.
|
|
58
|
+
|
|
59
|
+
:param int security_plan_id: The parent Security Plan ID
|
|
60
|
+
:rtype: ControlImplementation
|
|
61
|
+
:return: The control implementation
|
|
62
|
+
"""
|
|
63
|
+
# Create a lazily loaded reference to avoid circular imports, will only import once, and subsequent calls will
|
|
64
|
+
# use the already imported classes
|
|
65
|
+
ControlImplementation, ControlImplementationStatus = self._get_control_implementation_classes()
|
|
66
|
+
|
|
67
|
+
return ControlImplementation(
|
|
68
|
+
status=ControlImplementationStatus.NotImplemented.value,
|
|
69
|
+
controlID=self.controlID,
|
|
70
|
+
assessmentFrequency=365,
|
|
71
|
+
parentId=security_plan_id,
|
|
72
|
+
parentModule="securityplans",
|
|
73
|
+
)
|
|
74
|
+
|
|
36
75
|
@staticmethod
|
|
37
76
|
def _get_additional_endpoints() -> ConfigDict:
|
|
38
77
|
"""
|
|
@@ -62,14 +101,20 @@ class ProfileMapping(RegScaleModel):
|
|
|
62
101
|
:return: List of profile mappings
|
|
63
102
|
:rtype: List[ProfileMapping]
|
|
64
103
|
"""
|
|
104
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
105
|
+
|
|
65
106
|
response = cls._get_api_handler().get(
|
|
66
107
|
endpoint=cls.get_endpoint("get_by_profile").format(intProfileID=profile_id)
|
|
67
108
|
)
|
|
68
109
|
mappings = []
|
|
69
110
|
if response and response.ok:
|
|
70
111
|
mappings = [cls(**map) for map in response.json()]
|
|
71
|
-
|
|
72
|
-
|
|
112
|
+
with ThreadPoolExecutor() as executor:
|
|
113
|
+
futures = [
|
|
114
|
+
executor.submit(SecurityControl.get_object, object_id=mapping.controlID) for mapping in mappings
|
|
115
|
+
]
|
|
116
|
+
for future, mapping in zip(as_completed(futures), mappings):
|
|
117
|
+
if control := future.result():
|
|
73
118
|
mapping.control = control
|
|
74
119
|
return mappings
|
|
75
120
|
|
|
@@ -1315,6 +1315,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1315
1315
|
:param Optional[Progress] progress: Optional progress context for tracking
|
|
1316
1316
|
:param Optional[bool] remove_progress_bar: Whether to remove the progress bar after completion, defaults to False
|
|
1317
1317
|
"""
|
|
1318
|
+
# noqa: F824
|
|
1318
1319
|
nonlocal results
|
|
1319
1320
|
create_job = None
|
|
1320
1321
|
if progress:
|
|
@@ -1372,6 +1373,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1372
1373
|
:param Optional[Progress] progress: Optional progress context for tracking
|
|
1373
1374
|
:param Optional[bool] remove_progress_bar: Whether to remove the progress bar after completion, defaults to False
|
|
1374
1375
|
"""
|
|
1376
|
+
# noqa: F824
|
|
1375
1377
|
nonlocal results
|
|
1376
1378
|
update_job = None
|
|
1377
1379
|
if progress:
|
|
@@ -48,17 +48,18 @@ class Risk(RegScaleModel):
|
|
|
48
48
|
dateClosed: Optional[str] = None
|
|
49
49
|
facilityId: Optional[int] = None
|
|
50
50
|
orgId: Optional[int] = None
|
|
51
|
+
scenarioId: Optional[int] = None
|
|
51
52
|
comments: Optional[str] = None
|
|
52
53
|
riskTier: Optional[str] = None
|
|
53
54
|
title: Optional[str] = None
|
|
54
55
|
recommendations: Optional[str] = None
|
|
55
56
|
impactDescription: Optional[str] = None
|
|
56
|
-
inherentRiskScore: Optional[
|
|
57
|
-
residualRiskScore: Optional[
|
|
58
|
-
targetRiskScore: Optional[
|
|
59
|
-
difference: Optional[
|
|
60
|
-
futureCosts: Optional[
|
|
61
|
-
costToMitigate: Optional[
|
|
57
|
+
inherentRiskScore: Optional[float] = None
|
|
58
|
+
residualRiskScore: Optional[float] = None
|
|
59
|
+
targetRiskScore: Optional[float] = None
|
|
60
|
+
difference: Optional[float] = None
|
|
61
|
+
futureCosts: Optional[float] = None
|
|
62
|
+
costToMitigate: Optional[float] = None
|
|
62
63
|
controlId: Optional[int] = None
|
|
63
64
|
assessmentId: Optional[int] = None
|
|
64
65
|
requirementId: Optional[int] = None
|
|
@@ -73,6 +74,9 @@ class Risk(RegScaleModel):
|
|
|
73
74
|
nextAssessmentDueDate: Optional[str] = None
|
|
74
75
|
riskOwnerId: Optional[str] = None
|
|
75
76
|
isPublic: bool = True
|
|
77
|
+
averageRiskScore: Optional[float] = None
|
|
78
|
+
weightedRiskScore: Optional[float] = None
|
|
79
|
+
parentRiskModelId: Optional[int] = None
|
|
76
80
|
|
|
77
81
|
@staticmethod
|
|
78
82
|
def fetch_all_risks(app: Application) -> list["Risk"]:
|
|
@@ -145,31 +149,35 @@ class Risk(RegScaleModel):
|
|
|
145
149
|
"dateClosed": 26,
|
|
146
150
|
"facilityId": 27,
|
|
147
151
|
"orgId": 28,
|
|
148
|
-
"
|
|
149
|
-
"
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"
|
|
167
|
-
"
|
|
168
|
-
"
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
152
|
+
"scenarioId": 29,
|
|
153
|
+
"comments": 30,
|
|
154
|
+
"riskTier": 31,
|
|
155
|
+
"title": 32,
|
|
156
|
+
"recommendations": 33,
|
|
157
|
+
"impactDescription": 34,
|
|
158
|
+
"inherentRiskScore": 35,
|
|
159
|
+
"residualRiskScore": 36,
|
|
160
|
+
"targetRiskScore": 37,
|
|
161
|
+
"difference": 38,
|
|
162
|
+
"futureCosts": 39,
|
|
163
|
+
"costToMitigate": 40,
|
|
164
|
+
"controlId": 41,
|
|
165
|
+
"assessmentId": 42,
|
|
166
|
+
"requirementId": 43,
|
|
167
|
+
"securityPlanId": 44,
|
|
168
|
+
"projectId": 45,
|
|
169
|
+
"supplyChainId": 46,
|
|
170
|
+
"policyId": 47,
|
|
171
|
+
"componentId": 48,
|
|
172
|
+
"incidentId": 49,
|
|
173
|
+
"riskAssessmentFrequency": 50,
|
|
174
|
+
"dateLastAssessed": 51,
|
|
175
|
+
"nextAssessmentDueDate": 52,
|
|
176
|
+
"riskOwnerId": 53,
|
|
172
177
|
"isPublic": -1,
|
|
178
|
+
"averageRiskScore": 54,
|
|
179
|
+
"weightedRiskScore": 55,
|
|
180
|
+
"parentRiskModelId": 56,
|
|
173
181
|
}
|
|
174
182
|
|
|
175
183
|
# pylint: disable=W0613
|
|
@@ -106,7 +106,7 @@ class SupplyChain(RegScaleModel):
|
|
|
106
106
|
actualCosts: Optional[int] = 0
|
|
107
107
|
strategicTier: Optional[Union[SupplyChainTier, str]] = None
|
|
108
108
|
contractType: Optional[Union[SupplyChainContractType, str]] = None
|
|
109
|
-
scope: Optional[str] =
|
|
109
|
+
scope: Optional[str] = None
|
|
110
110
|
startDate: Optional[str] = None
|
|
111
111
|
endDate: Optional[str] = None
|
|
112
112
|
violationURL: Optional[str] = None
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
# standard python imports
|
|
6
6
|
import random
|
|
7
7
|
import string
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import List, Optional, cast
|
|
9
9
|
|
|
10
|
-
from pydantic import
|
|
10
|
+
from pydantic import ConfigDict, Field, field_validator
|
|
11
11
|
|
|
12
12
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
13
|
+
|
|
13
14
|
from .regscale_model import RegScaleModel, T
|
|
14
15
|
|
|
15
16
|
|
|
@@ -122,6 +123,19 @@ class User(RegScaleModel):
|
|
|
122
123
|
change_avatar="/api/{model_slug}/changeAvatar/{strUsername}",
|
|
123
124
|
)
|
|
124
125
|
|
|
126
|
+
@classmethod
|
|
127
|
+
def get_roles(cls) -> List[dict]:
|
|
128
|
+
"""
|
|
129
|
+
Get all roles from RegScale
|
|
130
|
+
|
|
131
|
+
:return: List of RegScale roles
|
|
132
|
+
:rtype: List[dict]
|
|
133
|
+
"""
|
|
134
|
+
response = cls._get_api_handler().get(endpoint=cls.get_endpoint("get_roles"))
|
|
135
|
+
if response and response.ok:
|
|
136
|
+
return response.json()
|
|
137
|
+
return []
|
|
138
|
+
|
|
125
139
|
@classmethod
|
|
126
140
|
def get_tenant_id_for_user_id(cls, user_id: str) -> Optional[int]:
|
|
127
141
|
"""
|
|
@@ -63,6 +63,16 @@ class ThreadSafeList(Generic[T]):
|
|
|
63
63
|
with self._lock:
|
|
64
64
|
return iter(self._list.copy())
|
|
65
65
|
|
|
66
|
+
def __list__(self) -> List[T]:
|
|
67
|
+
"""
|
|
68
|
+
Convert ThreadSafeList to a regular list when list() is called
|
|
69
|
+
|
|
70
|
+
:return: A copy of the internal list
|
|
71
|
+
:rtype: List[T]
|
|
72
|
+
"""
|
|
73
|
+
with self._lock:
|
|
74
|
+
return self._list.copy()
|
|
75
|
+
|
|
66
76
|
def remove(self, item: T) -> None:
|
|
67
77
|
"""
|
|
68
78
|
Remove an item from the list
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the ThreadSafeSet class, which is a thread-safe set.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from threading import RLock
|
|
6
|
+
from typing import Generic, Iterator, Optional, Set, TypeVar
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T") # Declare type variable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ThreadSafeSet(Generic[T]):
|
|
12
|
+
"""
|
|
13
|
+
ThreadSafeSet class to create a thread-safe set.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, initial_set: Optional[Set[T]] = None):
|
|
17
|
+
"""
|
|
18
|
+
Initialize a new ThreadSafeSet
|
|
19
|
+
|
|
20
|
+
:param Set[T]|None initial_set: Optional initial set to populate the ThreadSafeSet
|
|
21
|
+
"""
|
|
22
|
+
self._set: Set[T] = set(initial_set) if initial_set is not None else set()
|
|
23
|
+
self._lock = RLock()
|
|
24
|
+
|
|
25
|
+
def add(self, item: T) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Add an item to the set
|
|
28
|
+
|
|
29
|
+
:param T item: Item to add to the set
|
|
30
|
+
:rtype: None
|
|
31
|
+
"""
|
|
32
|
+
with self._lock:
|
|
33
|
+
self._set.add(item)
|
|
34
|
+
|
|
35
|
+
def remove(self, item: T) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Remove an item from the set
|
|
38
|
+
|
|
39
|
+
:param T item: Item to remove from the set
|
|
40
|
+
:raises KeyError: If the item is not found
|
|
41
|
+
:rtype: None
|
|
42
|
+
"""
|
|
43
|
+
with self._lock:
|
|
44
|
+
self._set.remove(item)
|
|
45
|
+
|
|
46
|
+
def discard(self, item: T) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Remove an item from the set if it exists
|
|
49
|
+
|
|
50
|
+
:param T item: Item to remove from the set
|
|
51
|
+
:rtype: None
|
|
52
|
+
"""
|
|
53
|
+
with self._lock:
|
|
54
|
+
self._set.discard(item)
|
|
55
|
+
|
|
56
|
+
def __len__(self) -> int:
|
|
57
|
+
"""
|
|
58
|
+
Get the length of the set
|
|
59
|
+
|
|
60
|
+
:return: The length of the set
|
|
61
|
+
:rtype: int
|
|
62
|
+
"""
|
|
63
|
+
with self._lock:
|
|
64
|
+
return len(self._set)
|
|
65
|
+
|
|
66
|
+
def __iter__(self) -> Iterator[T]:
|
|
67
|
+
"""
|
|
68
|
+
Return an iterator over the set
|
|
69
|
+
|
|
70
|
+
:return: An iterator over the set
|
|
71
|
+
:rtype: Iterator[T]
|
|
72
|
+
"""
|
|
73
|
+
with self._lock:
|
|
74
|
+
return iter(self._set.copy())
|
|
75
|
+
|
|
76
|
+
def __contains__(self, item: T) -> bool:
|
|
77
|
+
"""
|
|
78
|
+
Check if an item is in the set
|
|
79
|
+
|
|
80
|
+
:param T item: Item to check
|
|
81
|
+
:return: True if the item is in the set, False otherwise
|
|
82
|
+
:rtype: bool
|
|
83
|
+
"""
|
|
84
|
+
with self._lock:
|
|
85
|
+
return item in self._set
|
|
86
|
+
|
|
87
|
+
def clear(self) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Clear the set
|
|
90
|
+
|
|
91
|
+
:rtype: None
|
|
92
|
+
"""
|
|
93
|
+
with self._lock:
|
|
94
|
+
self._set.clear()
|
|
95
|
+
|
|
96
|
+
def union(self, other: Set[T]) -> Set[T]:
|
|
97
|
+
"""
|
|
98
|
+
Return the union of this set and another
|
|
99
|
+
|
|
100
|
+
:param Set[T] other: Other set to union with
|
|
101
|
+
:return: A new set containing the union
|
|
102
|
+
:rtype: Set[T]
|
|
103
|
+
"""
|
|
104
|
+
with self._lock:
|
|
105
|
+
return self._set.union(other)
|
|
106
|
+
|
|
107
|
+
def intersection(self, other: Set[T]) -> Set[T]:
|
|
108
|
+
"""
|
|
109
|
+
Return the intersection of this set and another
|
|
110
|
+
|
|
111
|
+
:param Set[T] other: Other set to intersect with
|
|
112
|
+
:return: A new set containing the intersection
|
|
113
|
+
:rtype: Set[T]
|
|
114
|
+
"""
|
|
115
|
+
with self._lock:
|
|
116
|
+
return self._set.intersection(other)
|