regscale-cli 6.20.9.1__py3-none-any.whl → 6.20.10.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/integrations/commercial/defender.py +9 -0
- regscale/integrations/commercial/wizv2/async_client.py +325 -0
- regscale/integrations/commercial/wizv2/constants.py +756 -0
- regscale/integrations/commercial/wizv2/scanner.py +1301 -89
- regscale/integrations/commercial/wizv2/utils.py +280 -36
- regscale/integrations/commercial/wizv2/variables.py +2 -10
- regscale/integrations/scanner_integration.py +58 -2
- regscale/integrations/variables.py +1 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/__init__.py +13 -0
- regscale/models/regscale_models/classification.py +23 -0
- regscale/models/regscale_models/cryptography.py +56 -0
- regscale/models/regscale_models/deviation.py +4 -4
- regscale/models/regscale_models/group.py +3 -2
- regscale/models/regscale_models/interconnection.py +1 -1
- regscale/models/regscale_models/issue.py +140 -41
- regscale/models/regscale_models/milestone.py +40 -0
- regscale/models/regscale_models/property.py +0 -1
- regscale/models/regscale_models/regscale_model.py +29 -18
- regscale/models/regscale_models/team.py +55 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/RECORD +29 -23
- tests/regscale/integrations/test_property_and_milestone_creation.py +684 -0
- tests/regscale/models/test_report.py +105 -29
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.9.1.dist-info → regscale_cli-6.20.10.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""RegScale models."""
|
|
2
2
|
|
|
3
3
|
from .assessment_plan import *
|
|
4
|
+
from .assessment_result import *
|
|
4
5
|
from .assessment import *
|
|
5
6
|
from .asset import *
|
|
6
7
|
from .asset_mapping import *
|
|
@@ -10,6 +11,7 @@ from .case import *
|
|
|
10
11
|
from .cci import *
|
|
11
12
|
from .change import *
|
|
12
13
|
from .checklist import *
|
|
14
|
+
from .classification import *
|
|
13
15
|
from .comment import *
|
|
14
16
|
from .compliance_settings import *
|
|
15
17
|
from .component import *
|
|
@@ -21,23 +23,32 @@ from .control_parameter import *
|
|
|
21
23
|
from .control_test import *
|
|
22
24
|
from .control_test_plan import *
|
|
23
25
|
from .control_test_result import *
|
|
26
|
+
from .cryptography import *
|
|
24
27
|
from .custom_field import *
|
|
25
28
|
from .data import *
|
|
26
29
|
from .data_center import *
|
|
30
|
+
from .deviation import *
|
|
27
31
|
from .email import *
|
|
28
32
|
from .evidence import *
|
|
29
33
|
from .evidence_mapping import *
|
|
30
34
|
from .facility import *
|
|
31
35
|
from .file import *
|
|
36
|
+
from .filetag import *
|
|
37
|
+
from .form_field_value import *
|
|
38
|
+
from .functional_roles import *
|
|
39
|
+
from .group import *
|
|
32
40
|
from .implementation_objective import *
|
|
33
41
|
from .implementation_option import *
|
|
42
|
+
from .implementation_role import *
|
|
34
43
|
from .incident import *
|
|
35
44
|
from .inherited_control import *
|
|
36
45
|
from .interconnection import *
|
|
37
46
|
from .issue import *
|
|
38
47
|
from .leveraged_authorization import *
|
|
48
|
+
from .line_of_inquiry import *
|
|
39
49
|
from .link import *
|
|
40
50
|
from .master_assessment import *
|
|
51
|
+
from .milestone import *
|
|
41
52
|
from .meta_data import *
|
|
42
53
|
from .objective import *
|
|
43
54
|
from .parameter import *
|
|
@@ -52,6 +63,7 @@ from .property import *
|
|
|
52
63
|
from .question import *
|
|
53
64
|
from .questionnaire import *
|
|
54
65
|
from .questionnaire_instance import *
|
|
66
|
+
from .rbac import *
|
|
55
67
|
from .reference import *
|
|
56
68
|
from .requirement import *
|
|
57
69
|
from .risk import *
|
|
@@ -69,6 +81,7 @@ from .system_role import *
|
|
|
69
81
|
from .system_role_external_assignment import *
|
|
70
82
|
from .task import *
|
|
71
83
|
from .threat import *
|
|
84
|
+
from .team import *
|
|
72
85
|
from .user import *
|
|
73
86
|
from .vulnerability import *
|
|
74
87
|
from .vulnerability_mapping import *
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for Classification Records in the application"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict
|
|
8
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ClassifiedRecord(RegScaleModel):
|
|
12
|
+
_module_slug = "classifiedRecords"
|
|
13
|
+
|
|
14
|
+
id: Optional[int] = None
|
|
15
|
+
parentRecordId: Optional[int] = 0
|
|
16
|
+
parentModule: Optional[str] = None
|
|
17
|
+
classificationTypeId: Optional[int] = 0
|
|
18
|
+
adjustedConfidentiality: Optional[str] = None
|
|
19
|
+
confidentialityJustification: Optional[str] = None
|
|
20
|
+
adjustedAvailability: Optional[str] = None
|
|
21
|
+
availabilityJustification: Optional[str] = None
|
|
22
|
+
adjustedIntegrity: Optional[str] = None
|
|
23
|
+
integrityJustification: Optional[str] = None
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for Cryptography in the application"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict, Field
|
|
8
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
9
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Cryptography(RegScaleModel):
|
|
13
|
+
_module_slug = "cryptography"
|
|
14
|
+
|
|
15
|
+
id: Optional[int] = None
|
|
16
|
+
cryptographyType: str
|
|
17
|
+
sourceArea: Optional[str] = None
|
|
18
|
+
sourceVendorName: Optional[str] = None
|
|
19
|
+
sourceModule: Optional[str] = None
|
|
20
|
+
fipsNumber: Optional[str] = None
|
|
21
|
+
bSourceEmbedded: Optional[bool] = False
|
|
22
|
+
bSourceThirdParty: Optional[bool] = False
|
|
23
|
+
bSourceOS: Optional[bool] = False
|
|
24
|
+
bSourceFIPS: Optional[bool] = False
|
|
25
|
+
bSourceOther: Optional[bool] = False
|
|
26
|
+
sourceExplanationOther: Optional[str] = None
|
|
27
|
+
destinationArea: Optional[str] = None
|
|
28
|
+
destinationVendorName: Optional[str] = None
|
|
29
|
+
destinationModule: Optional[str] = None
|
|
30
|
+
bDestinationEmbedded: Optional[bool] = False
|
|
31
|
+
bDestinationThirdParty: Optional[bool] = False
|
|
32
|
+
bDestinationOS: Optional[bool] = False
|
|
33
|
+
bDestinationFIPS: Optional[bool] = False
|
|
34
|
+
bDestinationOther: Optional[bool] = False
|
|
35
|
+
destinationExplanationOther: Optional[str] = None
|
|
36
|
+
bUsageTLS11: Optional[bool] = False
|
|
37
|
+
bUsageTLS12: Optional[bool] = False
|
|
38
|
+
bUsageTLS13: Optional[bool] = False
|
|
39
|
+
bUsageOther: Optional[bool] = False
|
|
40
|
+
usageExplanationOther: Optional[str] = None
|
|
41
|
+
usage: str
|
|
42
|
+
bRestFullDisk: Optional[bool] = False
|
|
43
|
+
bRestFile: Optional[bool] = False
|
|
44
|
+
bRestRecord: Optional[bool] = False
|
|
45
|
+
bRestNone: Optional[bool] = False
|
|
46
|
+
encryptionExplanationOther: Optional[str] = None
|
|
47
|
+
encryptionType: Optional[str] = None
|
|
48
|
+
notes: Optional[str] = None
|
|
49
|
+
referenceUrl: Optional[str] = None
|
|
50
|
+
isPublic: Optional[bool] = False
|
|
51
|
+
parentId: Optional[int] = 0
|
|
52
|
+
parentModule: Optional[str] = None
|
|
53
|
+
dateCreated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
54
|
+
lastUpdatedById: Optional[str] = Field(default_factory=RegScaleModel.get_user_id)
|
|
55
|
+
dateLastUpdated: Optional[str] = Field(default_factory=get_current_datetime)
|
|
56
|
+
tenantsId: int = 1
|
|
@@ -5,9 +5,7 @@ from typing import List, Optional
|
|
|
5
5
|
from pydantic import ConfigDict, Field
|
|
6
6
|
|
|
7
7
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
8
|
-
|
|
9
|
-
from .. import Property
|
|
10
|
-
from .regscale_model import RegScaleModel
|
|
8
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
11
9
|
|
|
12
10
|
logger = logging.getLogger("regscale")
|
|
13
11
|
|
|
@@ -143,7 +141,7 @@ class Deviation(RegScaleModel):
|
|
|
143
141
|
return []
|
|
144
142
|
|
|
145
143
|
@classmethod
|
|
146
|
-
def get_dr_ids(cls, issue_id: int) -> Optional[Property]:
|
|
144
|
+
def get_dr_ids(cls, issue_id: int) -> Optional["Property"]:
|
|
147
145
|
"""
|
|
148
146
|
Get a list of control implementations by plan ID.
|
|
149
147
|
|
|
@@ -151,6 +149,8 @@ class Deviation(RegScaleModel):
|
|
|
151
149
|
:return: A single Property object with the DR number if it exists or None
|
|
152
150
|
:rtype: Optional[Property]
|
|
153
151
|
"""
|
|
152
|
+
from regscale.models import Property
|
|
153
|
+
|
|
154
154
|
properties = [
|
|
155
155
|
prop
|
|
156
156
|
for prop in Property.get_all_by_parent(parent_id=issue_id, parent_module="issues")
|
|
@@ -8,11 +8,12 @@ from typing import Optional, List, Tuple, cast
|
|
|
8
8
|
from pydantic import ConfigDict, Field
|
|
9
9
|
|
|
10
10
|
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
11
|
-
from regscale.
|
|
11
|
+
from regscale.utils.decorators import deprecated
|
|
12
12
|
from regscale.models.regscale_models.regscale_model import RegScaleModel, T
|
|
13
|
+
from regscale.models.regscale_models.user import User
|
|
13
14
|
from regscale.models.regscale_models.user_group import UserGroup
|
|
14
15
|
|
|
15
|
-
logger = logging.getLogger(
|
|
16
|
+
logger = logging.getLogger("regscale")
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class Group(RegScaleModel):
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""Model for a RegScale Issue"""
|
|
4
4
|
import datetime
|
|
5
|
+
import time
|
|
5
6
|
from collections import defaultdict
|
|
6
7
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
7
8
|
from enum import Enum
|
|
@@ -25,6 +26,11 @@ from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
|
25
26
|
from regscale.utils.version import RegscaleVersion
|
|
26
27
|
|
|
27
28
|
|
|
29
|
+
# Module-level cache for open issues - avoids Pydantic conflicts
|
|
30
|
+
_OPEN_ISSUES_CACHE: Dict[int, Tuple[float, Dict[int, List["OpenIssueDict"]]]] = {}
|
|
31
|
+
_CACHE_TTL: float = 300.0 # 5 minutes TTL in seconds
|
|
32
|
+
|
|
33
|
+
|
|
28
34
|
class OpenIssueDict(TypedDict):
|
|
29
35
|
"""TypedDict for open issues"""
|
|
30
36
|
|
|
@@ -280,6 +286,59 @@ class Issue(RegScaleModel):
|
|
|
280
286
|
"find_by_integration_finding_id": "/api/{model_slug}/findByIntegrationFindingId/{id}",
|
|
281
287
|
}
|
|
282
288
|
|
|
289
|
+
@classmethod
|
|
290
|
+
def _is_cache_valid(cls, plan_id: int) -> bool:
|
|
291
|
+
"""
|
|
292
|
+
Check if cached data for a plan_id is still valid
|
|
293
|
+
|
|
294
|
+
:param int plan_id: The plan ID to check cache validity for
|
|
295
|
+
:return: True if cache is valid, False otherwise
|
|
296
|
+
:rtype: bool
|
|
297
|
+
"""
|
|
298
|
+
if plan_id not in _OPEN_ISSUES_CACHE:
|
|
299
|
+
return False
|
|
300
|
+
|
|
301
|
+
cached_time, _ = _OPEN_ISSUES_CACHE[plan_id]
|
|
302
|
+
return (time.time() - cached_time) < _CACHE_TTL
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def _get_from_cache(cls, plan_id: int) -> Optional[Dict[int, List[OpenIssueDict]]]:
|
|
306
|
+
"""
|
|
307
|
+
Get cached data for a plan_id if it exists and is valid
|
|
308
|
+
|
|
309
|
+
:param int plan_id: The plan ID to get cached data for
|
|
310
|
+
:return: Cached data if valid, None otherwise
|
|
311
|
+
:rtype: Optional[Dict[int, List[OpenIssueDict]]]
|
|
312
|
+
"""
|
|
313
|
+
if cls._is_cache_valid(plan_id):
|
|
314
|
+
_, cached_data = _OPEN_ISSUES_CACHE[plan_id]
|
|
315
|
+
return cached_data
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
@classmethod
|
|
319
|
+
def _cache_data(cls, plan_id: int, data: Dict[int, List[OpenIssueDict]]) -> None:
|
|
320
|
+
"""
|
|
321
|
+
Cache data for a plan_id
|
|
322
|
+
|
|
323
|
+
:param int plan_id: The plan ID to cache data for
|
|
324
|
+
:param Dict[int, List[OpenIssueDict]] data: The data to cache
|
|
325
|
+
:rtype: None
|
|
326
|
+
"""
|
|
327
|
+
_OPEN_ISSUES_CACHE[plan_id] = (time.time(), data)
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def clear_cache(cls, plan_id: Optional[int] = None) -> None:
|
|
331
|
+
"""
|
|
332
|
+
Clear cache for a specific plan_id or all cached data
|
|
333
|
+
|
|
334
|
+
:param Optional[int] plan_id: The plan ID to clear cache for, or None to clear all cache
|
|
335
|
+
:rtype: None
|
|
336
|
+
"""
|
|
337
|
+
if plan_id is not None:
|
|
338
|
+
_OPEN_ISSUES_CACHE.pop(plan_id, None)
|
|
339
|
+
else:
|
|
340
|
+
_OPEN_ISSUES_CACHE.clear()
|
|
341
|
+
|
|
283
342
|
@classmethod
|
|
284
343
|
def find_by_other_identifier(cls, other_identifier: str) -> List["Issue"]:
|
|
285
344
|
"""
|
|
@@ -826,70 +885,110 @@ class Issue(RegScaleModel):
|
|
|
826
885
|
cls, plan_id: int, is_component: Optional[bool] = False
|
|
827
886
|
) -> Dict[int, List[OpenIssueDict]]:
|
|
828
887
|
"""
|
|
829
|
-
Get all open issues by implementation id for a given security plan
|
|
888
|
+
Get all open issues by implementation id for a given security plan with optimized performance and caching
|
|
830
889
|
|
|
831
890
|
:param int plan_id: The ID of the parent
|
|
891
|
+
:param bool is_component: Optional[bool] Defaults to False
|
|
832
892
|
:return: A dictionary of control ids and their associated issue ids
|
|
833
893
|
:rtype: Dict[int, List[OpenIssueDict]]
|
|
834
894
|
"""
|
|
835
895
|
import logging
|
|
836
896
|
|
|
897
|
+
cache_disabled = cls._is_cache_disabled()
|
|
898
|
+
use_cache: bool = not cache_disabled
|
|
899
|
+
|
|
837
900
|
logger = logging.getLogger("regscale")
|
|
838
|
-
|
|
901
|
+
# Check cache first
|
|
902
|
+
if use_cache:
|
|
903
|
+
cached_data = cls._get_from_cache(plan_id)
|
|
904
|
+
if cached_data is not None:
|
|
905
|
+
logger.info(f"Using cached open issues data for security plan {plan_id}")
|
|
906
|
+
return cached_data
|
|
907
|
+
|
|
908
|
+
# Performance optimization: Use larger batch size and optimize query
|
|
909
|
+
take = 50 # Increased from 50 to reduce API roundtrips
|
|
839
910
|
skip = 0
|
|
840
911
|
control_issues: Dict[int, List[OpenIssueDict]] = defaultdict(list)
|
|
912
|
+
|
|
913
|
+
start_time = time.time()
|
|
914
|
+
logger.info(f"Fetching open issues for controls and for security plan {plan_id}...")
|
|
915
|
+
|
|
841
916
|
module_str = "component" if is_component else "security plan"
|
|
842
917
|
logger.info(
|
|
843
918
|
f"Fetching open issues for controls and for {module_str} {plan_id}...",
|
|
844
919
|
)
|
|
845
920
|
supports_multiple_controls: bool = cls.is_multiple_controls_supported()
|
|
846
921
|
|
|
847
|
-
#
|
|
848
|
-
|
|
849
|
-
id,
|
|
850
|
-
|
|
851
|
-
otherIdentifier
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
extra_fields="controlImplementations { id }" if supports_multiple_controls else ""
|
|
855
|
-
)
|
|
922
|
+
# Optimize field selection - only get what we need
|
|
923
|
+
if supports_multiple_controls:
|
|
924
|
+
fields = "id, otherIdentifier, integrationFindingId, controlImplementations { id }"
|
|
925
|
+
else:
|
|
926
|
+
fields = "id, controlId, otherIdentifier, integrationFindingId"
|
|
927
|
+
|
|
928
|
+
total_fetched = 0
|
|
856
929
|
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
query
|
|
860
|
-
{
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
930
|
+
try:
|
|
931
|
+
while True:
|
|
932
|
+
query = f"""
|
|
933
|
+
query GetOpenIssuesByPlanOrComponent {{
|
|
934
|
+
{cls.get_module_string()}(
|
|
935
|
+
skip: {skip},
|
|
936
|
+
take: {take},
|
|
937
|
+
where: {{
|
|
938
|
+
{"componentId" if is_component else "securityPlanId"}: {{eq: {plan_id}}},
|
|
939
|
+
status: {{eq: "Open"}}
|
|
940
|
+
}}
|
|
941
|
+
) {{
|
|
942
|
+
items {{ {fields} }}
|
|
943
|
+
pageInfo {{ hasNextPage }}
|
|
944
|
+
totalCount
|
|
866
945
|
}}
|
|
867
|
-
) {{
|
|
868
|
-
items {{ {fields} }}
|
|
869
|
-
pageInfo {{ hasNextPage }}
|
|
870
|
-
totalCount
|
|
871
946
|
}}
|
|
872
|
-
|
|
873
|
-
"""
|
|
947
|
+
"""
|
|
874
948
|
|
|
875
|
-
|
|
876
|
-
|
|
949
|
+
response = cls._get_api_handler().graph(query)
|
|
950
|
+
items = response.get(cls.get_module_string(), {}).get("items", [])
|
|
951
|
+
total_count = response.get(cls.get_module_string(), {}).get("totalCount", 0)
|
|
877
952
|
|
|
878
|
-
|
|
879
|
-
if
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
953
|
+
# Log progress for large datasets
|
|
954
|
+
if total_count > 1000:
|
|
955
|
+
logger.info(
|
|
956
|
+
f"Processing batch {skip // take + 1} - fetched {len(items)} items ({skip + len(items)}/{total_count})"
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
for item in items:
|
|
960
|
+
issue_dict = OpenIssueDict(
|
|
961
|
+
id=item["id"],
|
|
962
|
+
otherIdentifier=item.get("otherIdentifier", ""),
|
|
963
|
+
integrationFindingId=item.get("integrationFindingId", ""),
|
|
887
964
|
)
|
|
888
965
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
966
|
+
if supports_multiple_controls and item.get("controlImplementations"):
|
|
967
|
+
for control in item.get("controlImplementations", []):
|
|
968
|
+
control_issues[control["id"]].append(issue_dict)
|
|
969
|
+
elif item.get("controlId"):
|
|
970
|
+
control_issues[item["controlId"]].append(issue_dict)
|
|
971
|
+
|
|
972
|
+
total_fetched += len(items)
|
|
973
|
+
if not response.get(cls.get_module_string(), {}).get("pageInfo", {}).get("hasNextPage", False):
|
|
974
|
+
break
|
|
975
|
+
skip += take
|
|
976
|
+
|
|
977
|
+
except Exception as e:
|
|
978
|
+
logger.error(f"Error fetching open issues for security plan {plan_id}: {e}")
|
|
979
|
+
# Return empty dict on error to prevent breaking the calling code
|
|
980
|
+
return defaultdict(list)
|
|
981
|
+
|
|
982
|
+
elapsed_time = time.time() - start_time
|
|
983
|
+
logger.info(
|
|
984
|
+
f"Finished fetching {total_fetched} open issue(s) for {len(control_issues)} control(s) "
|
|
985
|
+
f"in security plan {plan_id} - took {elapsed_time:.2f} seconds"
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
# Cache the results
|
|
989
|
+
if use_cache:
|
|
990
|
+
cls._cache_data(plan_id, control_issues)
|
|
991
|
+
|
|
893
992
|
return control_issues
|
|
894
993
|
|
|
895
994
|
@classmethod
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Class for milestone model in RegScale platform"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from pydantic import ConfigDict, Field
|
|
7
|
+
|
|
8
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
9
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Milestone(RegScaleModel):
|
|
13
|
+
"""Milestone Model"""
|
|
14
|
+
|
|
15
|
+
_module_slug = "milestones"
|
|
16
|
+
_module_string = "milestones"
|
|
17
|
+
|
|
18
|
+
title: str
|
|
19
|
+
id: int = 0
|
|
20
|
+
isPublic: Optional[bool] = True
|
|
21
|
+
milestoneDate: Optional[str] = Field(default_factory=get_current_datetime)
|
|
22
|
+
responsiblePersonId: Optional[str] = None
|
|
23
|
+
predecessorStepId: Optional[int] = 0
|
|
24
|
+
completed: Optional[bool] = False
|
|
25
|
+
dateCompleted: Optional[str] = Field(default_factory=get_current_datetime)
|
|
26
|
+
notes: Optional[str] = ""
|
|
27
|
+
parentID: Optional[str] = None
|
|
28
|
+
parentModule: str = ""
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _get_additional_endpoints() -> ConfigDict:
|
|
32
|
+
"""
|
|
33
|
+
Get additional endpoints for the Milestone model
|
|
34
|
+
|
|
35
|
+
:return: A dictionary of additional endpoints
|
|
36
|
+
:rtype: ConfigDict
|
|
37
|
+
"""
|
|
38
|
+
return ConfigDict(
|
|
39
|
+
get_all_by_parent="/api/{model_slug}/getAllByParent/{intParentID}",
|
|
40
|
+
)
|
|
@@ -10,7 +10,6 @@ from pydantic import ConfigDict, Field, field_validator
|
|
|
10
10
|
|
|
11
11
|
from regscale.core.app.api import Api
|
|
12
12
|
from regscale.core.app.application import Application
|
|
13
|
-
from regscale.core.app.internal.model_editor import get_all_by_parent
|
|
14
13
|
from regscale.core.app.utils.app_utils import get_current_datetime, recursive_items
|
|
15
14
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
16
15
|
from regscale.utils import flatten_dict
|
|
@@ -32,8 +32,6 @@ 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
|
-
|
|
37
35
|
|
|
38
36
|
class RegScaleModel(BaseModel, ABC):
|
|
39
37
|
"""Mixin class for RegScale Models to add functionality to interact with RegScale API"""
|
|
@@ -58,6 +56,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
58
56
|
_parent_cache: ClassVar[Cache] = Cache(maxsize=50000)
|
|
59
57
|
_lock_registry: ClassVar[ThreadSafeDict] = ThreadSafeDict()
|
|
60
58
|
_global_lock: ClassVar[threading.Lock] = threading.Lock() # Class-level lock
|
|
59
|
+
_is_disable_cache: ClassVar[Optional[bool]] = None # Class-level cache setting
|
|
61
60
|
|
|
62
61
|
_pending_updates: ClassVar[Dict[str, Set[int]]] = {}
|
|
63
62
|
_pending_creations: ClassVar[Dict[str, Set[str]]] = {}
|
|
@@ -78,17 +77,19 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
78
77
|
:rtype: None
|
|
79
78
|
"""
|
|
80
79
|
try:
|
|
81
|
-
global IS_DISABLE_CACHE
|
|
82
80
|
super().__init__(*args, **data)
|
|
83
81
|
# Capture initial state after initialization
|
|
84
82
|
self._original_data = self.dict(exclude_unset=True)
|
|
85
|
-
|
|
86
|
-
if
|
|
83
|
+
# Initialize cache setting if not already set
|
|
84
|
+
if self.__class__._is_disable_cache is None:
|
|
85
|
+
self.__class__._is_disable_cache = self.__class__._fetch_disabled_cache_setting()
|
|
86
|
+
if self.__class__._is_disable_cache:
|
|
87
87
|
logger.debug("cache is disabled")
|
|
88
88
|
except Exception as e:
|
|
89
89
|
logger.error(f"Error creating {self.__class__.__name__}: {e} {data}", exc_info=True)
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
@classmethod
|
|
92
|
+
def _fetch_disabled_cache_setting(cls) -> bool:
|
|
92
93
|
"""
|
|
93
94
|
Check if caching is disabled based on the application config.
|
|
94
95
|
|
|
@@ -96,10 +97,22 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
96
97
|
:rtype: bool
|
|
97
98
|
"""
|
|
98
99
|
is_disabled = False
|
|
99
|
-
if config :=
|
|
100
|
+
if config := cls._get_api_handler().config:
|
|
100
101
|
is_disabled = config.get("disableCache", False)
|
|
101
102
|
return is_disabled
|
|
102
103
|
|
|
104
|
+
@classmethod
|
|
105
|
+
def _is_cache_disabled(cls) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Check if caching is disabled for this class.
|
|
108
|
+
|
|
109
|
+
:return: True if caching is disabled, False otherwise
|
|
110
|
+
:rtype: bool
|
|
111
|
+
"""
|
|
112
|
+
if cls._is_disable_cache is None:
|
|
113
|
+
cls._is_disable_cache = cls._fetch_disabled_cache_setting()
|
|
114
|
+
return cls._is_disable_cache
|
|
115
|
+
|
|
103
116
|
@classmethod
|
|
104
117
|
def disable_cache(cls) -> bool:
|
|
105
118
|
"""
|
|
@@ -108,9 +121,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
108
121
|
:return: True if caching is disabled, False otherwise
|
|
109
122
|
:rtype: bool
|
|
110
123
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return IS_DISABLE_CACHE
|
|
124
|
+
cls._is_disable_cache = True
|
|
125
|
+
return cls._is_disable_cache
|
|
114
126
|
|
|
115
127
|
@classmethod
|
|
116
128
|
def enable_cache(cls) -> bool:
|
|
@@ -120,9 +132,8 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
120
132
|
:return: True if caching is enabled, False otherwise
|
|
121
133
|
:rtype: bool
|
|
122
134
|
"""
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return IS_DISABLE_CACHE
|
|
135
|
+
cls._is_disable_cache = False
|
|
136
|
+
return cls._is_disable_cache
|
|
126
137
|
|
|
127
138
|
@classmethod
|
|
128
139
|
def _get_api_handler(cls) -> APIHandler:
|
|
@@ -210,7 +221,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
210
221
|
:return: The cached object if found, None otherwise
|
|
211
222
|
:rtype: Optional[T]
|
|
212
223
|
"""
|
|
213
|
-
if
|
|
224
|
+
if cls._is_cache_disabled():
|
|
214
225
|
return None
|
|
215
226
|
with cls._get_lock(cache_key):
|
|
216
227
|
return cls._object_cache.get(cache_key)
|
|
@@ -224,7 +235,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
224
235
|
:return: None
|
|
225
236
|
:rtype: None
|
|
226
237
|
"""
|
|
227
|
-
if
|
|
238
|
+
if cls._is_cache_disabled():
|
|
228
239
|
return
|
|
229
240
|
try:
|
|
230
241
|
if not obj:
|
|
@@ -274,7 +285,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
274
285
|
:return: None
|
|
275
286
|
:rtype: None
|
|
276
287
|
"""
|
|
277
|
-
if
|
|
288
|
+
if cls._is_cache_disabled():
|
|
278
289
|
return
|
|
279
290
|
parent_id = getattr(obj, cls._parent_id_field, None)
|
|
280
291
|
parent_module = getattr(obj, "parentModule", getattr(obj, "parent_module", ""))
|
|
@@ -299,7 +310,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
299
310
|
:return: None
|
|
300
311
|
:rtype: None
|
|
301
312
|
"""
|
|
302
|
-
if
|
|
313
|
+
if cls._is_cache_disabled():
|
|
303
314
|
return
|
|
304
315
|
with cls._get_lock(cache_key):
|
|
305
316
|
for obj in objects:
|
|
@@ -325,7 +336,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
325
336
|
:return: None
|
|
326
337
|
:rtype: None
|
|
327
338
|
"""
|
|
328
|
-
if
|
|
339
|
+
if cls._is_cache_disabled():
|
|
329
340
|
return
|
|
330
341
|
cache_key = cls._get_cache_key(obj)
|
|
331
342
|
with cls._get_lock(cache_key):
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Model for Teams in the application"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import ConfigDict
|
|
8
|
+
|
|
9
|
+
from regscale.core.app.api import Api
|
|
10
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Team(RegScaleModel):
|
|
14
|
+
_module_slug = "teams"
|
|
15
|
+
|
|
16
|
+
id: Optional[int] = None
|
|
17
|
+
member: Optional[str] = None
|
|
18
|
+
memberId: Optional[str] = None
|
|
19
|
+
memberType: Optional[str] = None
|
|
20
|
+
bExecutive: Optional[bool] = False
|
|
21
|
+
bOversight: Optional[bool] = False
|
|
22
|
+
bCommunications: Optional[bool] = False
|
|
23
|
+
bEngineer: Optional[bool] = False
|
|
24
|
+
bAssessor: Optional[bool] = False
|
|
25
|
+
bQuality: Optional[bool] = False
|
|
26
|
+
bSafety: Optional[bool] = False
|
|
27
|
+
bSecurity: Optional[bool] = False
|
|
28
|
+
bAnalyst: Optional[bool] = False
|
|
29
|
+
bScheduler: Optional[bool] = False
|
|
30
|
+
bAdministrative: Optional[bool] = False
|
|
31
|
+
bProjectManager: Optional[bool] = False
|
|
32
|
+
bFinance: Optional[bool] = False
|
|
33
|
+
bHumanResources: Optional[bool] = False
|
|
34
|
+
bOperations: Optional[bool] = False
|
|
35
|
+
bISSO: Optional[bool] = False
|
|
36
|
+
bISSM: Optional[bool] = False
|
|
37
|
+
bAO: Optional[bool] = False
|
|
38
|
+
bCISO: Optional[bool] = False
|
|
39
|
+
bSCA: Optional[bool] = False
|
|
40
|
+
bDevOps: Optional[bool] = False
|
|
41
|
+
bProgrammer: Optional[bool] = False
|
|
42
|
+
bSystemOwner: Optional[bool] = False
|
|
43
|
+
bRiskAnalyst: Optional[bool] = False
|
|
44
|
+
bInternalAudit: Optional[bool] = False
|
|
45
|
+
bExternalAudit: Optional[bool] = False
|
|
46
|
+
bInformationOwner: Optional[bool] = False
|
|
47
|
+
bAODR: Optional[bool] = False
|
|
48
|
+
bContingencyCoordinator: Optional[bool] = False
|
|
49
|
+
bContingencyDirector: Optional[bool] = False
|
|
50
|
+
bResponsible: Optional[bool] = False
|
|
51
|
+
bAccountable: Optional[bool] = False
|
|
52
|
+
bConsulted: Optional[bool] = False
|
|
53
|
+
bInformed: Optional[bool] = False
|
|
54
|
+
parentId: Optional[int] = None
|
|
55
|
+
parentModule: Optional[str] = None
|