regscale-cli 6.19.1.0__py3-none-any.whl → 6.20.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/__init__.py +1 -1
- regscale/airflow/config.py +2 -0
- regscale/airflow/tasks/groups.py +11 -47
- regscale/core/app/internal/login.py +49 -43
- regscale/core/app/internal/model_editor.py +2 -1
- regscale/dev/code_gen.py +2 -5
- regscale/integrations/commercial/amazon/common.py +5 -4
- regscale/integrations/commercial/aws/scanner.py +3 -2
- regscale/integrations/commercial/synqly/assets.py +20 -0
- regscale/integrations/commercial/synqly/ticketing.py +25 -0
- regscale/integrations/commercial/wizv2/click.py +3 -3
- regscale/integrations/public/fedramp/appendix_parser.py +499 -104
- regscale/integrations/public/fedramp/fedramp_five.py +89 -43
- regscale/integrations/scanner_integration.py +1 -1
- regscale/models/app_models/import_validater.py +2 -0
- regscale/models/integration_models/cisa_kev_data.json +355 -27
- regscale/models/integration_models/flat_file_importer/__init__.py +26 -9
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/__init__.py +5 -0
- regscale/models/regscale_models/business_impact_assessment.py +71 -0
- regscale/models/regscale_models/control_implementation.py +15 -0
- regscale/models/regscale_models/master_assessment.py +19 -0
- regscale/models/regscale_models/policy.py +90 -0
- regscale/models/regscale_models/question.py +30 -2
- regscale/models/regscale_models/questionnaire.py +4 -3
- regscale/models/regscale_models/questionnaire_instance.py +37 -14
- regscale/models/regscale_models/rbac.py +0 -1
- regscale/models/regscale_models/regscale_model.py +16 -15
- regscale/models/regscale_models/risk_trend.py +67 -0
- regscale/utils/graphql_client.py +2 -1
- {regscale_cli-6.19.1.0.dist-info → regscale_cli-6.20.0.0.dist-info}/METADATA +130 -71
- {regscale_cli-6.19.1.0.dist-info → regscale_cli-6.20.0.0.dist-info}/RECORD +36 -33
- {regscale_cli-6.19.1.0.dist-info → regscale_cli-6.20.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.19.1.0.dist-info → regscale_cli-6.20.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.19.1.0.dist-info → regscale_cli-6.20.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.19.1.0.dist-info → regscale_cli-6.20.0.0.dist-info}/top_level.txt +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from .assessment import *
|
|
4
4
|
from .asset import *
|
|
5
5
|
from .asset_mapping import *
|
|
6
|
+
from .business_impact_assessment import *
|
|
6
7
|
from .catalog import *
|
|
7
8
|
from .cci import *
|
|
8
9
|
from .change import *
|
|
@@ -32,9 +33,11 @@ from .interconnection import *
|
|
|
32
33
|
from .issue import *
|
|
33
34
|
from .leveraged_authorization import *
|
|
34
35
|
from .link import *
|
|
36
|
+
from .master_assessment import *
|
|
35
37
|
from .meta_data import *
|
|
36
38
|
from .objective import *
|
|
37
39
|
from .parameter import *
|
|
40
|
+
from .policy import *
|
|
38
41
|
from .ports_protocol import *
|
|
39
42
|
from .privacy import *
|
|
40
43
|
from .profile import *
|
|
@@ -48,6 +51,7 @@ from .questionnaire_instance import *
|
|
|
48
51
|
from .reference import *
|
|
49
52
|
from .requirement import *
|
|
50
53
|
from .risk import *
|
|
54
|
+
from .risk_trend import *
|
|
51
55
|
from .sbom import *
|
|
52
56
|
from .scan_history import *
|
|
53
57
|
from .security_control import *
|
|
@@ -55,6 +59,7 @@ from .security_plan import *
|
|
|
55
59
|
from .software_inventory import *
|
|
56
60
|
from .stake_holder import *
|
|
57
61
|
from .stig import *
|
|
62
|
+
from .supply_chain import *
|
|
58
63
|
from .system_role import *
|
|
59
64
|
from .system_role_external_assignment import *
|
|
60
65
|
from .task import *
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the Business Impact Assessment model for RegScale.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict
|
|
8
|
+
|
|
9
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BusinessImpactAssessment(RegScaleModel):
|
|
13
|
+
"""RegScale Business Impact Assessment
|
|
14
|
+
|
|
15
|
+
:return: RegScale Business Impact Assessment
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
_module_slug = "business-impact-assessments"
|
|
19
|
+
_module_string = "business-impact-assessments"
|
|
20
|
+
# Should we include baseline, ruleId, check, and results in unique fields?
|
|
21
|
+
_unique_fields = [
|
|
22
|
+
[
|
|
23
|
+
"riskId",
|
|
24
|
+
"category",
|
|
25
|
+
],
|
|
26
|
+
]
|
|
27
|
+
_parent_id_field = "riskId"
|
|
28
|
+
# Required
|
|
29
|
+
id: int = 0
|
|
30
|
+
isPublic: bool = True
|
|
31
|
+
uuid: str = ""
|
|
32
|
+
category: str = ""
|
|
33
|
+
probability: str = ""
|
|
34
|
+
consequence: str = ""
|
|
35
|
+
notes: str = ""
|
|
36
|
+
riskType: str = ""
|
|
37
|
+
riskScore: int = 0
|
|
38
|
+
riskId: int = 0
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def _get_additional_endpoints() -> ConfigDict:
|
|
42
|
+
"""
|
|
43
|
+
Get additional endpoints for the Catalogues model
|
|
44
|
+
|
|
45
|
+
:return: A dictionary of additional endpoints
|
|
46
|
+
:rtype: ConfigDict
|
|
47
|
+
"""
|
|
48
|
+
return ConfigDict( # type: ignore
|
|
49
|
+
get_all_by_risk="/api/{model_slug}/getAllByRisk/{intID}",
|
|
50
|
+
get_all_by_risk_and_type="/api/{model_slug}/getAllByRiskAndType/{intID}/{strType}",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def get_all_by_risk(cls, risk_id: int) -> List["BusinessImpactAssessment"]:
|
|
55
|
+
"""
|
|
56
|
+
Get all Business Impact Assessments by risk ID
|
|
57
|
+
"""
|
|
58
|
+
endpoint = cls.get_endpoint("get_all_by_risk").format(model_slug=cls.get_module_slug(), intID=risk_id)
|
|
59
|
+
res = cls._handle_list_response(cls._get_api_handler().get(endpoint))
|
|
60
|
+
return res
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def get_all_by_risk_and_type(cls, risk_id: int, risk_type: str) -> List["BusinessImpactAssessment"]:
|
|
64
|
+
"""
|
|
65
|
+
Get all Business Impact Assessments by risk ID and type
|
|
66
|
+
"""
|
|
67
|
+
endpoint = cls.get_endpoint("get_all_by_risk_and_type").format(
|
|
68
|
+
model_slug=cls.get_module_slug(), intID=risk_id, strType=risk_type
|
|
69
|
+
)
|
|
70
|
+
res = cls._handle_list_response(cls._get_api_handler().get(endpoint))
|
|
71
|
+
return res
|
|
@@ -39,6 +39,21 @@ class ControlImplementationStatus(str, Enum):
|
|
|
39
39
|
Alternative = "Alternate Implementation"
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
class ImplementationControlOrigin(str, Enum):
|
|
43
|
+
SERVICE_PROVIDER_CORPORATE = "Service Provider Corporate"
|
|
44
|
+
SERVICE_PROVIDER_SYSTEM = "Service Provider System Specific"
|
|
45
|
+
SERVICE_PROVIDER_HYBRID = "Service Provider Hybrid (Corporate and System Specific)"
|
|
46
|
+
CONFIGURED_BY_CUSTOMER = "Configured by Customer (Customer System Specific)"
|
|
47
|
+
PROVIDED_BY_CUSTOMER = "Provided by Customer (Customer Specific)"
|
|
48
|
+
SHARED = "Shared (Service Provider and Customer Responsibility)"
|
|
49
|
+
INHERITED_FROM_PRE_EXISTING_FEDRAMP_AUTHORIZATION = (
|
|
50
|
+
"Inherited from pre-existing FedRAMP Authorization" # noqa: E501
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def __str__(self):
|
|
54
|
+
return self.value
|
|
55
|
+
|
|
56
|
+
|
|
42
57
|
class ControlImplementationOrigin(str, Enum):
|
|
43
58
|
"""Control Origination"""
|
|
44
59
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""Model for a RegScale Assessment"""
|
|
4
|
+
from enum import Enum
|
|
4
5
|
from typing import List, Optional
|
|
5
6
|
|
|
6
7
|
from pydantic import ConfigDict, Field
|
|
@@ -9,6 +10,13 @@ from regscale.models.regscale_models.assessment import Assessment
|
|
|
9
10
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
10
11
|
|
|
11
12
|
|
|
13
|
+
class MasterAssessmentStatus(Enum):
|
|
14
|
+
"""Enum representing different status states with string values."""
|
|
15
|
+
|
|
16
|
+
InProgress = "In Progress"
|
|
17
|
+
Completed = "Completed"
|
|
18
|
+
|
|
19
|
+
|
|
12
20
|
class MasterAssessment(RegScaleModel):
|
|
13
21
|
"""
|
|
14
22
|
Model for a RegScale Assessment
|
|
@@ -125,3 +133,14 @@ class MasterAssessment(RegScaleModel):
|
|
|
125
133
|
if ci := cls.get_object(object_id=ci["id"]):
|
|
126
134
|
assessments.append(ci)
|
|
127
135
|
return assessments
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def is_date_field(cls, field_name: str) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Overrides the base method.
|
|
141
|
+
|
|
142
|
+
:param str field_name: The property name to provide enum values for
|
|
143
|
+
:return: bool if the field should be formatted as a date
|
|
144
|
+
:rtype: bool
|
|
145
|
+
"""
|
|
146
|
+
return field_name in ["plannedStart", "plannedFinish", "actualFinish"]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""This module contains the Policy models."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import ConfigDict
|
|
7
|
+
|
|
8
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PolicyParameter(RegScaleModel):
|
|
12
|
+
"""Policy Parameter Model"""
|
|
13
|
+
|
|
14
|
+
_module_slug = "policyparameter"
|
|
15
|
+
_unique_fields = [
|
|
16
|
+
["policyId", "name"],
|
|
17
|
+
]
|
|
18
|
+
id: int = 0
|
|
19
|
+
policyId: int
|
|
20
|
+
name: str = ""
|
|
21
|
+
value: str = ""
|
|
22
|
+
default: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Policy(RegScaleModel):
|
|
26
|
+
"""Policy Model"""
|
|
27
|
+
|
|
28
|
+
_module_slug = "policies"
|
|
29
|
+
_unique_fields = [
|
|
30
|
+
["integrationFindingId", "vulnerabilityId", "status"],
|
|
31
|
+
["otherIdentifier", "parentModule", "parentId", "status"],
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
id: int = 0
|
|
35
|
+
policyNumber: str = ""
|
|
36
|
+
policyOwnerId: str = ""
|
|
37
|
+
policyType: str = ""
|
|
38
|
+
dateApproved: str = ""
|
|
39
|
+
expirationDate: str = ""
|
|
40
|
+
status: str = ""
|
|
41
|
+
title: str = ""
|
|
42
|
+
description: str = ""
|
|
43
|
+
practiceLevel: str = ""
|
|
44
|
+
processLevel: str = ""
|
|
45
|
+
facilityId: Optional[int] = None
|
|
46
|
+
orgId: int = 0
|
|
47
|
+
parentModule: str = ""
|
|
48
|
+
parentId: int = 0
|
|
49
|
+
isPublic: bool = True
|
|
50
|
+
policyTemplate: str = ""
|
|
51
|
+
policyTemplateId: str = ""
|
|
52
|
+
policyParameters: List[PolicyParameter] = []
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def _get_additional_endpoints() -> ConfigDict:
|
|
56
|
+
"""
|
|
57
|
+
Get additional endpoints for the ProfileMapping model, using {model_slug} as a placeholder for the model slug.
|
|
58
|
+
|
|
59
|
+
:return: A dictionary of additional endpoints
|
|
60
|
+
:rtype: ConfigDict
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
return ConfigDict( # type: ignore
|
|
64
|
+
get_list="/api/{model_slug}/getList",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def get_list(cls) -> List[dict]:
|
|
69
|
+
"""
|
|
70
|
+
Get a list of policies.
|
|
71
|
+
|
|
72
|
+
:return: A list of policies
|
|
73
|
+
:rtype: List[dict]
|
|
74
|
+
"""
|
|
75
|
+
endpoint = cls.get_endpoint("get_list")
|
|
76
|
+
res = cls._get_api_handler().get(endpoint=endpoint)
|
|
77
|
+
return res.json()
|
|
78
|
+
|
|
79
|
+
def convert_datetime_to_date_str(self, dt: Optional[datetime]) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Convert a datetime object to a date string in the format 'MMM DD, YYYY'.
|
|
82
|
+
|
|
83
|
+
:param dt: The datetime object to convert
|
|
84
|
+
:type dt: Optional[datetime]
|
|
85
|
+
:return: The date string in format 'MMM DD, YYYY'
|
|
86
|
+
:rtype: str
|
|
87
|
+
"""
|
|
88
|
+
if dt is None:
|
|
89
|
+
return ""
|
|
90
|
+
return dt.strftime("%b %d, %Y")
|
|
@@ -3,9 +3,13 @@ This module contains the Questions model for RegScale.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from enum import Enum
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Dict, List, Optional
|
|
7
|
+
|
|
7
8
|
from pydantic import ConfigDict, Field
|
|
9
|
+
|
|
10
|
+
from regscale.core.app.utils.api_handler import APIRetrieveError
|
|
8
11
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
12
|
+
|
|
9
13
|
from .regscale_model import RegScaleModel
|
|
10
14
|
|
|
11
15
|
|
|
@@ -62,6 +66,30 @@ class Questions(RegScaleModel):
|
|
|
62
66
|
uploadEnabled: bool = False
|
|
63
67
|
response: Optional[str] = None # Adjust the type if it's not a string
|
|
64
68
|
|
|
69
|
+
@classmethod
|
|
70
|
+
def get_all_by_parent(cls, parent_questionnaire_id: int) -> List["Questions"]:
|
|
71
|
+
"""
|
|
72
|
+
Retrieve all questions associated with a parent questionnaire.
|
|
73
|
+
|
|
74
|
+
:param parent_questionnaire_id: The ID of the parent questionnaire to fetch questions for.
|
|
75
|
+
:return: A list of Questions objects associated with the parent questionnaire.
|
|
76
|
+
:raises ValueError: If parent_questionnaire_id is less than or equal to 0.
|
|
77
|
+
:raises APIRetrieveError: If the API request fails.
|
|
78
|
+
"""
|
|
79
|
+
if parent_questionnaire_id <= 0:
|
|
80
|
+
raise ValueError("parent_questionnaire_id must be a positive integer")
|
|
81
|
+
|
|
82
|
+
params = {"parentId": parent_questionnaire_id}
|
|
83
|
+
endpoint = cls.get_endpoint("get_all_by_parent").format(
|
|
84
|
+
model_slug=cls._module_slug,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
response = cls._get_api_handler().get(endpoint=endpoint, params=params)
|
|
88
|
+
if not response.ok:
|
|
89
|
+
raise APIRetrieveError(f"Failed to fetch questions: {response.status_code} - {response.text}")
|
|
90
|
+
|
|
91
|
+
return [cls(**ques) for ques in response.json()]
|
|
92
|
+
|
|
65
93
|
@staticmethod
|
|
66
94
|
def _get_additional_endpoints() -> ConfigDict:
|
|
67
95
|
"""
|
|
@@ -71,7 +99,7 @@ class Questions(RegScaleModel):
|
|
|
71
99
|
:rtype: ConfigDict
|
|
72
100
|
"""
|
|
73
101
|
return ConfigDict(
|
|
74
|
-
|
|
102
|
+
get_all_by_parent="/api/{model_slug}/getAllByParent",
|
|
75
103
|
get="/api/{model_slug}/find/{id}",
|
|
76
104
|
insert="/api/{model_slug}/create",
|
|
77
105
|
update="/api/{model_slug}/update",
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
# pylint: disable=C0301
|
|
1
2
|
"""
|
|
2
3
|
This module contains the Questionnaires model in RegScale.
|
|
3
4
|
"""
|
|
4
5
|
|
|
5
6
|
import logging
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import List, Optional
|
|
7
8
|
|
|
8
9
|
from pydantic import ConfigDict
|
|
9
10
|
|
|
@@ -19,6 +20,7 @@ class Questionnaires(RegScaleModel):
|
|
|
19
20
|
"""
|
|
20
21
|
|
|
21
22
|
_module_slug = "questionnaires"
|
|
23
|
+
_unique_fields = [["title", "parentQuestionnaireId"]]
|
|
22
24
|
|
|
23
25
|
id: Optional[int] = 0
|
|
24
26
|
uuid: Optional[str] = None
|
|
@@ -82,6 +84,5 @@ class Questionnaires(RegScaleModel):
|
|
|
82
84
|
if response.ok:
|
|
83
85
|
response_json = response.json()
|
|
84
86
|
return [QuestionnaireInstance(**instance) for instance in response_json.get("createdInstances", [])]
|
|
85
|
-
|
|
86
|
-
logger.info(f"Failed to create instances from questionnaires {response.status_code} - {response.text}")
|
|
87
|
+
logger.info("Failed to create instances from questionnaires %i - %s", response.status_code, response.text)
|
|
87
88
|
return None
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
This module contains the QuestionnaireInstances model.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import logging
|
|
5
6
|
from enum import Enum
|
|
6
|
-
from typing import
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
7
8
|
|
|
8
|
-
from pydantic import
|
|
9
|
+
from pydantic import ConfigDict, Field
|
|
10
|
+
from requests import Response
|
|
9
11
|
|
|
10
12
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
11
13
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
12
|
-
import logging
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -113,26 +114,48 @@ class QuestionnaireInstance(RegScaleModel):
|
|
|
113
114
|
return None
|
|
114
115
|
|
|
115
116
|
@classmethod
|
|
116
|
-
def
|
|
117
|
+
def _handle_response(cls, response: Optional[Response]) -> Optional[Dict[str, Any]]:
|
|
117
118
|
"""
|
|
118
|
-
|
|
119
|
+
Helper method to handle API responses consistently.
|
|
119
120
|
|
|
120
|
-
:param
|
|
121
|
-
:return: The response
|
|
121
|
+
:param Optional[Response] response: The response from the API
|
|
122
|
+
:return: The JSON response if successful, None otherwise
|
|
122
123
|
:rtype: Optional[Dict[str, Any]]
|
|
123
124
|
"""
|
|
124
|
-
endpoint = cls.get_endpoint("link_feedback").format(model_slug=cls._module_slug, id=id)
|
|
125
|
-
response = cls._get_api_handler().get(endpoint)
|
|
126
|
-
|
|
127
125
|
if not response or response.status_code in [204, 404]:
|
|
128
126
|
return None
|
|
129
127
|
if response.ok:
|
|
130
|
-
|
|
131
|
-
return response.json()
|
|
132
|
-
except Exception:
|
|
133
|
-
return None
|
|
128
|
+
return response.json()
|
|
134
129
|
return None
|
|
135
130
|
|
|
131
|
+
@classmethod
|
|
132
|
+
def submit_for_feedback(cls, quid: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
133
|
+
"""
|
|
134
|
+
Submits a questionnaire instance for feedback.
|
|
135
|
+
|
|
136
|
+
:param str quid: The QUID of the questionnaire instance
|
|
137
|
+
:param Dict[str, Any] payload: The data to be sent in the request body
|
|
138
|
+
:return: The response from the API or None
|
|
139
|
+
:rtype: Optional[Dict[str, Any]]
|
|
140
|
+
"""
|
|
141
|
+
endpoint = cls.get_endpoint("submit_for_feedback").format(model_slug=cls._module_slug, uuid=quid)
|
|
142
|
+
response = cls._get_api_handler().put(endpoint, data=payload)
|
|
143
|
+
return cls._handle_response(response)
|
|
144
|
+
|
|
145
|
+
@classmethod
|
|
146
|
+
def update_response(cls, quid: str, payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
147
|
+
"""
|
|
148
|
+
Updates questionnaire instance responses.
|
|
149
|
+
|
|
150
|
+
:param str quid: The QUID of the questionnaire instance
|
|
151
|
+
:param Dict[str, Any] payload: The data to be sent in the request body containing instance and questions
|
|
152
|
+
:return: The response from the API or None
|
|
153
|
+
:rtype: Optional[Dict[str, Any]]
|
|
154
|
+
"""
|
|
155
|
+
endpoint = cls.get_endpoint("update_responses").format(model_slug=cls._module_slug, uuid=quid)
|
|
156
|
+
response = cls._get_api_handler().put(endpoint, data=payload)
|
|
157
|
+
return cls._handle_response(response)
|
|
158
|
+
|
|
136
159
|
@classmethod
|
|
137
160
|
def get_all_by_parent(cls, parent_questionnaire_id: int) -> List["QuestionnaireInstance"]:
|
|
138
161
|
"""
|
|
@@ -32,6 +32,8 @@ T = TypeVar("T", bound="RegScaleModel")
|
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger("regscale")
|
|
34
34
|
|
|
35
|
+
IS_DISABLE_CACHE = False # flag to disable caching
|
|
36
|
+
|
|
35
37
|
|
|
36
38
|
class RegScaleModel(BaseModel, ABC):
|
|
37
39
|
"""Mixin class for RegScale Models to add functionality to interact with RegScale API"""
|
|
@@ -52,8 +54,6 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
52
54
|
_exclude_graphql_fields: ClassVar[List[str]] = ["extra_data", "tenantsId"]
|
|
53
55
|
_original_data: Optional[Dict[str, Any]] = None
|
|
54
56
|
|
|
55
|
-
# Caching
|
|
56
|
-
_disable_cache: bool = False # flag to disable caching
|
|
57
57
|
_object_cache: ClassVar[Cache] = Cache(maxsize=100000)
|
|
58
58
|
_parent_cache: ClassVar[Cache] = Cache(maxsize=50000)
|
|
59
59
|
_lock_registry: ClassVar[ThreadSafeDict] = ThreadSafeDict()
|
|
@@ -78,11 +78,12 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
78
78
|
:rtype: None
|
|
79
79
|
"""
|
|
80
80
|
try:
|
|
81
|
+
global IS_DISABLE_CACHE
|
|
81
82
|
super().__init__(*args, **data)
|
|
82
83
|
# Capture initial state after initialization
|
|
83
84
|
self._original_data = self.dict(exclude_unset=True)
|
|
84
|
-
|
|
85
|
-
if
|
|
85
|
+
IS_DISABLE_CACHE = self._fetch_disabled_cache_setting()
|
|
86
|
+
if IS_DISABLE_CACHE:
|
|
86
87
|
logger.debug("cache is disabled")
|
|
87
88
|
except Exception as e:
|
|
88
89
|
logger.error(f"Error creating {self.__class__.__name__}: {e} {data}", exc_info=True)
|
|
@@ -107,8 +108,9 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
107
108
|
:return: True if caching is disabled, False otherwise
|
|
108
109
|
:rtype: bool
|
|
109
110
|
"""
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
global IS_DISABLE_CACHE
|
|
112
|
+
IS_DISABLE_CACHE = True
|
|
113
|
+
return IS_DISABLE_CACHE
|
|
112
114
|
|
|
113
115
|
@classmethod
|
|
114
116
|
def enable_cache(cls) -> bool:
|
|
@@ -118,8 +120,9 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
118
120
|
:return: True if caching is enabled, False otherwise
|
|
119
121
|
:rtype: bool
|
|
120
122
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
global IS_DISABLE_CACHE
|
|
124
|
+
IS_DISABLE_CACHE = False
|
|
125
|
+
return IS_DISABLE_CACHE
|
|
123
126
|
|
|
124
127
|
@classmethod
|
|
125
128
|
def _get_api_handler(cls) -> APIHandler:
|
|
@@ -207,7 +210,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
207
210
|
:return: The cached object if found, None otherwise
|
|
208
211
|
:rtype: Optional[T]
|
|
209
212
|
"""
|
|
210
|
-
if
|
|
213
|
+
if IS_DISABLE_CACHE:
|
|
211
214
|
return None
|
|
212
215
|
with cls._get_lock(cache_key):
|
|
213
216
|
return cls._object_cache.get(cache_key)
|
|
@@ -221,7 +224,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
221
224
|
:return: None
|
|
222
225
|
:rtype: None
|
|
223
226
|
"""
|
|
224
|
-
if
|
|
227
|
+
if IS_DISABLE_CACHE:
|
|
225
228
|
return
|
|
226
229
|
try:
|
|
227
230
|
if not obj:
|
|
@@ -271,7 +274,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
271
274
|
:return: None
|
|
272
275
|
:rtype: None
|
|
273
276
|
"""
|
|
274
|
-
if
|
|
277
|
+
if IS_DISABLE_CACHE:
|
|
275
278
|
return
|
|
276
279
|
parent_id = getattr(obj, cls._parent_id_field, None)
|
|
277
280
|
parent_module = getattr(obj, "parentModule", getattr(obj, "parent_module", ""))
|
|
@@ -296,7 +299,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
296
299
|
:return: None
|
|
297
300
|
:rtype: None
|
|
298
301
|
"""
|
|
299
|
-
if
|
|
302
|
+
if IS_DISABLE_CACHE:
|
|
300
303
|
return
|
|
301
304
|
with cls._get_lock(cache_key):
|
|
302
305
|
for obj in objects:
|
|
@@ -311,8 +314,6 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
311
314
|
:return: None
|
|
312
315
|
:rtype: None
|
|
313
316
|
"""
|
|
314
|
-
if cls._disable_cache:
|
|
315
|
-
return
|
|
316
317
|
cls._object_cache.clear()
|
|
317
318
|
|
|
318
319
|
@classmethod
|
|
@@ -324,7 +325,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
324
325
|
:return: None
|
|
325
326
|
:rtype: None
|
|
326
327
|
"""
|
|
327
|
-
if
|
|
328
|
+
if IS_DISABLE_CACHE:
|
|
328
329
|
return
|
|
329
330
|
cache_key = cls._get_cache_key(obj)
|
|
330
331
|
with cls._get_lock(cache_key):
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the Risk Trends model for RegScale.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RiskTrend(RegScaleModel):
|
|
11
|
+
"""RegScale Risk Trends
|
|
12
|
+
|
|
13
|
+
:return: RegScale Business Impact Assessment
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
_module_slug = "riskTrends"
|
|
17
|
+
_module_string = "riskTrends"
|
|
18
|
+
# Should we include baseline, ruleId, check, and results in unique fields?
|
|
19
|
+
_unique_fields = [
|
|
20
|
+
[
|
|
21
|
+
"riskId",
|
|
22
|
+
"category",
|
|
23
|
+
],
|
|
24
|
+
]
|
|
25
|
+
_parent_id_field = "riskId"
|
|
26
|
+
# Required fields (Nullable = "NO")
|
|
27
|
+
id: int
|
|
28
|
+
analysis: str = ""
|
|
29
|
+
riskTrend: str = ""
|
|
30
|
+
costImpact: float = 0.0
|
|
31
|
+
scheduleImpact: int = 0
|
|
32
|
+
dateAssessed: str = ""
|
|
33
|
+
riskId: int
|
|
34
|
+
isPublic: bool = True
|
|
35
|
+
difference: float = 0.0
|
|
36
|
+
inherentConsequence: str
|
|
37
|
+
inherentProbability: str
|
|
38
|
+
inherentRiskScore: float = 0.0
|
|
39
|
+
|
|
40
|
+
residualConsequence: str
|
|
41
|
+
residualProbability: str
|
|
42
|
+
residualRisk: str
|
|
43
|
+
residualRiskScore: float = 0.0
|
|
44
|
+
|
|
45
|
+
targetRiskScore: float = 0.0
|
|
46
|
+
|
|
47
|
+
annualLossExpectancy: float = 0.0
|
|
48
|
+
|
|
49
|
+
expectedLost: float = 0.0
|
|
50
|
+
|
|
51
|
+
lossEventFrequency: float = 0.0
|
|
52
|
+
|
|
53
|
+
maximumLoss: float = 0.0
|
|
54
|
+
|
|
55
|
+
minimumLoss: float = 0.0
|
|
56
|
+
|
|
57
|
+
riskExposure: float = 0.0
|
|
58
|
+
threatEventFrequency: float = 0.0
|
|
59
|
+
|
|
60
|
+
# Optional fields (Nullable = "YES")
|
|
61
|
+
uuid: Optional[str] = None
|
|
62
|
+
mitigationEffectiveness: Optional[str] = None
|
|
63
|
+
threatChanges: Optional[str] = None
|
|
64
|
+
impactDescription: Optional[str] = None
|
|
65
|
+
operationalRequirements: Optional[str] = None
|
|
66
|
+
riskStrategy: Optional[str] = None
|
|
67
|
+
assumptions: Optional[str] = None
|
regscale/utils/graphql_client.py
CHANGED
|
@@ -27,6 +27,7 @@ class PaginatedGraphQLClient:
|
|
|
27
27
|
headers: Optional[Dict[str, str]] = None,
|
|
28
28
|
logging_level: str = logging.CRITICAL,
|
|
29
29
|
) -> None:
|
|
30
|
+
from regscale.integrations.variables import ScannerVariables
|
|
30
31
|
from gql import gql, Client # Optimize import performance
|
|
31
32
|
from gql.transport.requests import RequestsHTTPTransport
|
|
32
33
|
from gql.transport.requests import log as requests_logger
|
|
@@ -35,7 +36,7 @@ class PaginatedGraphQLClient:
|
|
|
35
36
|
self.endpoint = endpoint
|
|
36
37
|
self.query = gql(query)
|
|
37
38
|
self.headers = headers or {} # Ensure headers are a dictionary
|
|
38
|
-
self.transport = RequestsHTTPTransport(url=endpoint, headers=self.headers)
|
|
39
|
+
self.transport = RequestsHTTPTransport(url=endpoint, headers=self.headers, verify=ScannerVariables.sslVerify)
|
|
39
40
|
self.client = Client(transport=self.transport)
|
|
40
41
|
self.job_progress = create_progress_object()
|
|
41
42
|
requests_logger.setLevel(level=self.log_level)
|