regscale-cli 6.23.0.1__py3-none-any.whl → 6.24.0.1__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 +2 -0
- regscale/integrations/commercial/__init__.py +1 -0
- regscale/integrations/commercial/jira.py +95 -22
- regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +132 -2
- regscale/integrations/commercial/wizv2/compliance_report.py +1574 -0
- regscale/integrations/commercial/wizv2/constants.py +72 -2
- regscale/integrations/commercial/wizv2/data_fetcher.py +61 -0
- regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
- regscale/integrations/commercial/wizv2/issue.py +775 -27
- regscale/integrations/commercial/wizv2/policy_compliance.py +599 -181
- regscale/integrations/commercial/wizv2/reports.py +243 -0
- regscale/integrations/commercial/wizv2/scanner.py +668 -245
- regscale/integrations/compliance_integration.py +534 -56
- regscale/integrations/due_date_handler.py +210 -0
- regscale/integrations/public/cci_importer.py +444 -0
- regscale/integrations/scanner_integration.py +718 -153
- regscale/models/integration_models/CCI_List.xml +1 -0
- regscale/models/integration_models/cisa_kev_data.json +18 -3
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/control_implementation.py +13 -3
- regscale/models/regscale_models/form_field_value.py +1 -1
- regscale/models/regscale_models/milestone.py +1 -0
- regscale/models/regscale_models/regscale_model.py +225 -60
- regscale/models/regscale_models/security_plan.py +3 -2
- regscale/regscale.py +7 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/METADATA +17 -17
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/RECORD +45 -28
- tests/fixtures/test_fixture.py +13 -8
- tests/regscale/integrations/public/__init__.py +0 -0
- tests/regscale/integrations/public/test_alienvault.py +220 -0
- tests/regscale/integrations/public/test_cci.py +458 -0
- tests/regscale/integrations/public/test_cisa.py +1021 -0
- tests/regscale/integrations/public/test_emass.py +518 -0
- tests/regscale/integrations/public/test_fedramp.py +851 -0
- tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
- tests/regscale/integrations/public/test_file_uploads.py +506 -0
- tests/regscale/integrations/public/test_oscal.py +453 -0
- tests/regscale/models/test_form_field_value_integration.py +304 -0
- tests/regscale/models/test_module_integration.py +582 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# standard python imports
|
|
5
5
|
import logging
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from typing import Any, Callable, Dict, List, Optional, Union
|
|
7
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
8
8
|
from urllib.parse import urljoin
|
|
9
9
|
|
|
10
10
|
import requests
|
|
@@ -19,7 +19,6 @@ from regscale.models.regscale_models.implementation_role import ImplementationRo
|
|
|
19
19
|
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
20
20
|
from regscale.models.regscale_models.security_control import SecurityControl
|
|
21
21
|
|
|
22
|
-
|
|
23
22
|
logger = logging.getLogger("regscale")
|
|
24
23
|
PATCH_CONTENT_TYPE = "application/json-patch+json"
|
|
25
24
|
|
|
@@ -74,6 +73,7 @@ class ControlImplementation(RegScaleModel):
|
|
|
74
73
|
_get_objects_for_list = True
|
|
75
74
|
|
|
76
75
|
controlOwnerId: str = Field(default_factory=RegScaleModel.get_user_id)
|
|
76
|
+
controlOwnersIds: Optional[List[str]] = Field(default=None)
|
|
77
77
|
status: str # Required
|
|
78
78
|
controlID: int # Required foreign key to Security Control
|
|
79
79
|
status_lst: List[ControlImplementationStatus] = []
|
|
@@ -153,9 +153,13 @@ class ControlImplementation(RegScaleModel):
|
|
|
153
153
|
"""
|
|
154
154
|
self.status_lst = self._get_status_enum()
|
|
155
155
|
|
|
156
|
+
# Backwards compatibility: Auto-populate controlOwnersIds if not set but controlOwnerId exists
|
|
157
|
+
if self.controlOwnersIds is None and self.controlOwnerId:
|
|
158
|
+
self.controlOwnersIds = [self.controlOwnerId]
|
|
159
|
+
|
|
156
160
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
157
161
|
"""
|
|
158
|
-
Override __setattr__ to update status_lst when status changes.
|
|
162
|
+
Override __setattr__ to update status_lst when status changes and handle backwards compatibility.
|
|
159
163
|
|
|
160
164
|
:param str name: The attribute name
|
|
161
165
|
:param Any value: The attribute value
|
|
@@ -164,6 +168,12 @@ class ControlImplementation(RegScaleModel):
|
|
|
164
168
|
super().__setattr__(name, value)
|
|
165
169
|
if name == "status":
|
|
166
170
|
self.status_lst = self._get_status_enum()
|
|
171
|
+
elif name == "controlOwnerId" and value:
|
|
172
|
+
# Backwards compatibility: Auto-populate controlOwnersIds when controlOwnerId is set
|
|
173
|
+
if hasattr(self, "controlOwnersIds") and (
|
|
174
|
+
not hasattr(self, "_controlOwnersIds") or self._controlOwnersIds is None
|
|
175
|
+
):
|
|
176
|
+
super().__setattr__("controlOwnersIds", [value])
|
|
167
177
|
|
|
168
178
|
@classmethod
|
|
169
179
|
def _get_additional_endpoints(cls) -> ConfigDict:
|
|
@@ -148,7 +148,7 @@ class FormFieldValue(RegScaleModel):
|
|
|
148
148
|
]
|
|
149
149
|
if data:
|
|
150
150
|
FormFieldValue.save_custom_data(
|
|
151
|
-
record_id=form_field_value.get
|
|
151
|
+
record_id=form_field_value.get("record_id"),
|
|
152
152
|
module_name=form_field_value.get("record_module", "securityplans"),
|
|
153
153
|
data=data,
|
|
154
154
|
)
|
|
@@ -502,31 +502,93 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
502
502
|
:return: A list of unique fields
|
|
503
503
|
:rtype: List[List[str]]
|
|
504
504
|
"""
|
|
505
|
-
sample_format = {"uniqueOverride": {"asset": ["ipAddress"]}}
|
|
506
505
|
config = Application().config
|
|
507
506
|
|
|
507
|
+
# First, ensure _unique_fields is in new format (List[List[str]])
|
|
508
|
+
cls._ensure_unique_fields_format()
|
|
509
|
+
|
|
508
510
|
try:
|
|
509
511
|
primary = config.get("uniqueOverride", {}).get(cls.__name__.lower())
|
|
510
512
|
if primary:
|
|
511
|
-
|
|
512
|
-
raise ValueError(
|
|
513
|
-
f"Invalid config format in uniqueOverride.{cls.__name__.lower()}, the configuration must be in a format like so:\n{dump(sample_format, default_flow_style=False)}"
|
|
514
|
-
)
|
|
515
|
-
if primary != cls._unique_fields:
|
|
516
|
-
if all(attr in cls.model_fields for attr in primary):
|
|
517
|
-
if primary not in cls._unique_fields:
|
|
518
|
-
cls._unique_fields.insert(1, primary)
|
|
519
|
-
else:
|
|
520
|
-
# Move primary to index 1 if it exists
|
|
521
|
-
cls._unique_fields.insert(1, cls._unique_fields.pop(cls._unique_fields.index(primary)))
|
|
522
|
-
else:
|
|
523
|
-
raise ValueError(
|
|
524
|
-
f"One or more invalid attribute(s) detected: {primary}, falling back on default unique fields for type: {cls.__name__.lower()}"
|
|
525
|
-
)
|
|
513
|
+
cls._process_primary_override(primary)
|
|
526
514
|
except ValueError as e:
|
|
527
515
|
logger.warning(e)
|
|
528
516
|
return cls._unique_fields
|
|
529
517
|
|
|
518
|
+
@classmethod
|
|
519
|
+
def _ensure_unique_fields_format(cls) -> None:
|
|
520
|
+
"""
|
|
521
|
+
Ensure _unique_fields is in the new format (List[List[str]]).
|
|
522
|
+
|
|
523
|
+
:rtype: None
|
|
524
|
+
"""
|
|
525
|
+
# Check if it's still in old format (List[str])
|
|
526
|
+
if cls._unique_fields and isinstance(cls._unique_fields[0], str):
|
|
527
|
+
# Convert old format to new format
|
|
528
|
+
cls._unique_fields = [cls._unique_fields] # type: ignore
|
|
529
|
+
|
|
530
|
+
@classmethod
|
|
531
|
+
def _process_primary_override(cls, primary: List[str]) -> None:
|
|
532
|
+
"""
|
|
533
|
+
Process the primary override configuration.
|
|
534
|
+
|
|
535
|
+
:param List[str] primary: The primary override fields
|
|
536
|
+
:raises ValueError: If the primary fields are invalid
|
|
537
|
+
:rtype: None
|
|
538
|
+
"""
|
|
539
|
+
cls._validate_primary_format(primary)
|
|
540
|
+
|
|
541
|
+
# Now cls._unique_fields is guaranteed to be List[List[str]]
|
|
542
|
+
# Check if primary is different from any existing unique field set
|
|
543
|
+
if primary not in cls._unique_fields:
|
|
544
|
+
cls._handle_new_primary_fields(primary)
|
|
545
|
+
|
|
546
|
+
@classmethod
|
|
547
|
+
def _validate_primary_format(cls, primary: List[str]) -> None:
|
|
548
|
+
"""
|
|
549
|
+
Validate the format of the primary override configuration.
|
|
550
|
+
|
|
551
|
+
:param List[str] primary: The primary override fields
|
|
552
|
+
:raises ValueError: If the primary format is invalid
|
|
553
|
+
:rtype: None
|
|
554
|
+
"""
|
|
555
|
+
if not isinstance(primary, list):
|
|
556
|
+
sample_format = {"uniqueOverride": {"asset": ["ipAddress"]}}
|
|
557
|
+
raise ValueError(
|
|
558
|
+
f"Invalid config format in uniqueOverride.{cls.__name__.lower()}, the configuration must be in a format like so:\n{dump(sample_format, default_flow_style=False)}"
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
@classmethod
|
|
562
|
+
def _handle_new_primary_fields(cls, primary: List[str]) -> None:
|
|
563
|
+
"""
|
|
564
|
+
Handle new primary fields that are not in existing unique fields.
|
|
565
|
+
|
|
566
|
+
:param List[str] primary: The primary override fields
|
|
567
|
+
:raises ValueError: If any attributes are invalid
|
|
568
|
+
:rtype: None
|
|
569
|
+
"""
|
|
570
|
+
if all(attr in cls.model_fields for attr in primary):
|
|
571
|
+
cls._insert_primary_fields(primary)
|
|
572
|
+
else:
|
|
573
|
+
raise ValueError(
|
|
574
|
+
f"One or more invalid attribute(s) detected: {primary}, falling back on default unique fields for type: {cls.__name__.lower()}"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
@classmethod
|
|
578
|
+
def _insert_primary_fields(cls, primary: List[str]) -> None:
|
|
579
|
+
"""
|
|
580
|
+
Insert primary fields into the unique fields list.
|
|
581
|
+
|
|
582
|
+
:param List[str] primary: The primary override fields
|
|
583
|
+
:rtype: None
|
|
584
|
+
"""
|
|
585
|
+
# Check if primary already exists in the list
|
|
586
|
+
if primary not in cls._unique_fields:
|
|
587
|
+
cls._unique_fields.insert(1, primary)
|
|
588
|
+
else:
|
|
589
|
+
# Move primary to index 1 if it exists
|
|
590
|
+
cls._unique_fields.insert(1, cls._unique_fields.pop(cls._unique_fields.index(primary)))
|
|
591
|
+
|
|
530
592
|
@classmethod
|
|
531
593
|
def _get_endpoints(cls) -> ConfigDict:
|
|
532
594
|
"""
|
|
@@ -665,17 +727,45 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
665
727
|
self.cache_object(instance)
|
|
666
728
|
return False, instance
|
|
667
729
|
else:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
730
|
+
try:
|
|
731
|
+
created_instance = self.create(bulk=bulk)
|
|
732
|
+
self.cache_object(created_instance)
|
|
733
|
+
return True, created_instance
|
|
734
|
+
except APIInsertionError as e:
|
|
735
|
+
# Check if this is a duplicate error (race condition in threading)
|
|
736
|
+
error_str = str(e).lower()
|
|
737
|
+
if "already exists" in error_str or "mapping already exists" in error_str:
|
|
738
|
+
logger.debug(
|
|
739
|
+
f"Race condition detected for {self.__class__.__name__}, retrying find_by_unique: {e}"
|
|
740
|
+
)
|
|
741
|
+
# Clear the cache to force a fresh lookup
|
|
742
|
+
self.clear_cache()
|
|
743
|
+
# Try to find the instance again - another thread may have created it
|
|
744
|
+
instance = self.find_by_unique()
|
|
745
|
+
if instance:
|
|
746
|
+
self.cache_object(instance)
|
|
747
|
+
logger.debug(
|
|
748
|
+
f"Successfully found existing {self.__class__.__name__} after duplicate creation error, ID: {instance.id}"
|
|
749
|
+
)
|
|
750
|
+
return False, instance
|
|
751
|
+
else:
|
|
752
|
+
# If we still can't find it, log error but don't stop the process
|
|
753
|
+
logger.error(
|
|
754
|
+
f"Failed to find {self.__class__.__name__} after duplicate creation error: {e}"
|
|
755
|
+
)
|
|
756
|
+
# Return None to indicate creation failed but don't raise
|
|
757
|
+
return False, None
|
|
758
|
+
else:
|
|
759
|
+
# Not a duplicate error, re-raise
|
|
760
|
+
logger.error(f"Failed to create object: {self.__class__.__name__} creation error: {e}")
|
|
671
761
|
|
|
672
|
-
def get_or_create(self: T, bulk: bool = False) -> T:
|
|
762
|
+
def get_or_create(self: T, bulk: bool = False) -> Optional[T]:
|
|
673
763
|
"""
|
|
674
764
|
Get or create an instance.
|
|
675
765
|
|
|
676
766
|
:param bool bulk: Whether to perform a bulk create operation, defaults to False
|
|
677
|
-
:return: The instance
|
|
678
|
-
:rtype: T
|
|
767
|
+
:return: The instance or None if creation failed due to race condition
|
|
768
|
+
:rtype: Optional[T]
|
|
679
769
|
"""
|
|
680
770
|
_, instance = self.get_or_create_with_status(bulk=bulk)
|
|
681
771
|
return instance
|
|
@@ -727,44 +817,100 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
727
817
|
instance = cached_object or self.find_by_unique()
|
|
728
818
|
|
|
729
819
|
if instance:
|
|
730
|
-
|
|
731
|
-
logger.debug(f"Found {'cached' if cached_object else 'existing'} instance of {self.__class__.__name__}")
|
|
732
|
-
# Update the current object's ID with the found instance's ID
|
|
733
|
-
self.id = instance.id
|
|
734
|
-
# If the object has a 'dateCreated' attribute, update it
|
|
735
|
-
if hasattr(self, "dateCreated"):
|
|
736
|
-
self.dateCreated = instance.dateCreated # noqa
|
|
737
|
-
|
|
738
|
-
# Update the _original_data attribute with the instance data
|
|
739
|
-
self._original_data = instance.dict(exclude_unset=True)
|
|
740
|
-
|
|
741
|
-
# Check if the current object has any changes compared to the found instance
|
|
742
|
-
if self.has_changed():
|
|
743
|
-
logger.debug(f"Instance of {self.__class__.__name__} has changed, updating")
|
|
744
|
-
# Save the changes, potentially in bulk
|
|
745
|
-
updated_instance = self.save(bulk=bulk_update)
|
|
746
|
-
# Update the cache with the new instance
|
|
747
|
-
self.cache_object(updated_instance)
|
|
748
|
-
# Return the updated instance, optionally with a flag indicating it wasn't newly created
|
|
749
|
-
return False, updated_instance
|
|
750
|
-
|
|
751
|
-
# If no changes, return the existing instance
|
|
752
|
-
return False, instance
|
|
820
|
+
return self._handle_existing_instance(instance, cached_object, bulk_update)
|
|
753
821
|
|
|
754
822
|
# No existing instance was found, so create a new one
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
823
|
+
return self._handle_new_instance(defaults, bulk_create)
|
|
824
|
+
|
|
825
|
+
def _handle_existing_instance(
|
|
826
|
+
self: T, instance: T, cached_object: Optional[T], bulk_update: bool
|
|
827
|
+
) -> Tuple[bool, T]:
|
|
828
|
+
"""
|
|
829
|
+
Handle processing of an existing instance found in cache or database.
|
|
830
|
+
|
|
831
|
+
:param T instance: The found instance
|
|
832
|
+
:param Optional[T] cached_object: The cached object if found in cache
|
|
833
|
+
:param bool bulk_update: Whether to perform a bulk update
|
|
834
|
+
:return: Tuple of (was_created, instance)
|
|
835
|
+
:rtype: Tuple[bool, T]
|
|
836
|
+
"""
|
|
837
|
+
# An existing instance was found (either in cache or database)
|
|
838
|
+
logger.debug(f"Found {'cached' if cached_object else 'existing'} instance of {self.__class__.__name__}")
|
|
839
|
+
|
|
840
|
+
# Update current object with instance data
|
|
841
|
+
self._sync_with_existing_instance(instance)
|
|
842
|
+
|
|
843
|
+
# Check if the current object has any changes compared to the found instance
|
|
844
|
+
if self.has_changed():
|
|
845
|
+
return self._update_existing_instance(bulk_update)
|
|
846
|
+
|
|
847
|
+
# If no changes, return the existing instance
|
|
848
|
+
return False, instance
|
|
849
|
+
|
|
850
|
+
def _sync_with_existing_instance(self: T, instance: T) -> None:
|
|
851
|
+
"""
|
|
852
|
+
Synchronize current object with existing instance data.
|
|
853
|
+
|
|
854
|
+
:param T instance: The existing instance to sync with
|
|
855
|
+
:rtype: None
|
|
856
|
+
"""
|
|
857
|
+
# Update the current object's ID with the found instance's ID
|
|
858
|
+
self.id = instance.id
|
|
859
|
+
# If the object has a 'dateCreated' attribute, update it
|
|
860
|
+
if hasattr(self, "dateCreated"):
|
|
861
|
+
self.dateCreated = instance.dateCreated # noqa
|
|
862
|
+
|
|
863
|
+
# Update the _original_data attribute with the instance data
|
|
864
|
+
self._original_data = instance.dict(exclude_unset=True)
|
|
865
|
+
|
|
866
|
+
def _update_existing_instance(self: T, bulk_update: bool) -> Tuple[bool, T]:
|
|
867
|
+
"""
|
|
868
|
+
Update an existing instance that has changes.
|
|
869
|
+
|
|
870
|
+
:param bool bulk_update: Whether to perform a bulk update
|
|
871
|
+
:return: Tuple of (was_created, updated_instance)
|
|
872
|
+
:rtype: Tuple[bool, T]
|
|
873
|
+
"""
|
|
874
|
+
logger.debug(f"Instance of {self.__class__.__name__} has changed, updating")
|
|
875
|
+
# Save the changes, potentially in bulk
|
|
876
|
+
updated_instance = self.save(bulk=bulk_update)
|
|
877
|
+
# Update the cache with the new instance
|
|
878
|
+
self.cache_object(updated_instance)
|
|
879
|
+
# Return the updated instance, optionally with a flag indicating it wasn't newly created
|
|
880
|
+
return False, updated_instance
|
|
881
|
+
|
|
882
|
+
def _handle_new_instance(self: T, defaults: Optional[Dict[str, Any]], bulk_create: bool) -> Tuple[bool, T]:
|
|
883
|
+
"""
|
|
884
|
+
Handle creation of a new instance when none exists.
|
|
885
|
+
|
|
886
|
+
:param Optional[Dict[str, Any]] defaults: Dictionary of default values to apply
|
|
887
|
+
:param bool bulk_create: Whether to perform a bulk create
|
|
888
|
+
:return: Tuple of (was_created, created_instance)
|
|
889
|
+
:rtype: Tuple[bool, T]
|
|
890
|
+
"""
|
|
891
|
+
# apply defaults if they are provided
|
|
892
|
+
self._apply_defaults(defaults)
|
|
893
|
+
|
|
894
|
+
logger.debug(f"No existing instance found for {self.__class__.__name__}, creating new")
|
|
895
|
+
created_instance = self.create(bulk=bulk_create)
|
|
896
|
+
# Cache the newly created instance
|
|
897
|
+
self.cache_object(created_instance)
|
|
898
|
+
# Return the created instance, optionally with a flag indicating it was newly created
|
|
899
|
+
return True, created_instance
|
|
900
|
+
|
|
901
|
+
def _apply_defaults(self: T, defaults: Optional[Dict[str, Any]]) -> None:
|
|
902
|
+
"""
|
|
903
|
+
Apply default values to the instance.
|
|
904
|
+
|
|
905
|
+
:param Optional[Dict[str, Any]] defaults: Dictionary of default values to apply
|
|
906
|
+
:rtype: None
|
|
907
|
+
"""
|
|
908
|
+
if defaults:
|
|
909
|
+
for key, value in defaults.items():
|
|
910
|
+
# Handle callable values by executing them
|
|
911
|
+
if callable(value):
|
|
912
|
+
value = value()
|
|
913
|
+
setattr(self, key, value)
|
|
768
914
|
|
|
769
915
|
@classmethod
|
|
770
916
|
def _handle_list_response(
|
|
@@ -1269,7 +1415,15 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1269
1415
|
endpoint = self.get_endpoint("insert")
|
|
1270
1416
|
response = self._get_api_handler().post(endpoint=endpoint, data=self.dict(), headers=self._get_headers())
|
|
1271
1417
|
if response and response.ok:
|
|
1272
|
-
|
|
1418
|
+
response_data = response.json()
|
|
1419
|
+
|
|
1420
|
+
# Handle special case for ComponentMapping which may have nested response structure
|
|
1421
|
+
if self.__class__.__name__ == "ComponentMapping" and "componentMapping" in response_data:
|
|
1422
|
+
component_mapping_data = response_data["componentMapping"]
|
|
1423
|
+
obj = self.__class__(**component_mapping_data)
|
|
1424
|
+
else:
|
|
1425
|
+
obj = self.__class__(**response_data)
|
|
1426
|
+
|
|
1273
1427
|
self.cache_object(obj)
|
|
1274
1428
|
return obj
|
|
1275
1429
|
else:
|
|
@@ -1468,6 +1622,17 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1468
1622
|
logger.warning(f"{cls.__name__}: No matching record found for ID: {cls.__name__} {object_id}")
|
|
1469
1623
|
return None
|
|
1470
1624
|
|
|
1625
|
+
@classmethod
|
|
1626
|
+
def get(cls, id: Union[str, int]) -> Optional[T]:
|
|
1627
|
+
"""
|
|
1628
|
+
Get a RegScale object by ID. shortcut for get_object.
|
|
1629
|
+
|
|
1630
|
+
:param Union[str, int] id: The ID of the object
|
|
1631
|
+
:return: The object or None if not found
|
|
1632
|
+
:rtype: Optional[T]
|
|
1633
|
+
"""
|
|
1634
|
+
return cls.get_object(object_id=id)
|
|
1635
|
+
|
|
1471
1636
|
@classmethod
|
|
1472
1637
|
def get_objects_and_attachments_by_parent(
|
|
1473
1638
|
cls, parent_id: int, parent_module: str
|
|
@@ -1480,7 +1645,7 @@ class RegScaleModel(BaseModel, ABC):
|
|
|
1480
1645
|
:return: A tuple of a list of objects and a list of attachments
|
|
1481
1646
|
:rtype: Tuple[List[T], dict[int, List["File"]]]
|
|
1482
1647
|
"""
|
|
1483
|
-
from regscale.models import File
|
|
1648
|
+
from regscale.models.regscale_models import File
|
|
1484
1649
|
|
|
1485
1650
|
# get the existing issues for the parent record that are already in RegScale
|
|
1486
1651
|
logger.info("Fetching full %s list from RegScale %s #%i.", cls.__name__, parent_module, parent_id)
|
|
@@ -107,6 +107,7 @@ class SecurityPlan(RegScaleModel):
|
|
|
107
107
|
fedrampDateSubmitted: Optional[str] = ""
|
|
108
108
|
fedrampDateAuthorized: Optional[str] = ""
|
|
109
109
|
fedrampId: Optional[str] = ""
|
|
110
|
+
complianceSettings: Optional[str] = None
|
|
110
111
|
complianceSettingsId: Optional[int] = 1
|
|
111
112
|
tenantsId: int = 1
|
|
112
113
|
|
|
@@ -119,11 +120,11 @@ class SecurityPlan(RegScaleModel):
|
|
|
119
120
|
:return: The ComplianceSettings ID if the RegScale version is compatible, None otherwise
|
|
120
121
|
:rtype: Optional[int]
|
|
121
122
|
"""
|
|
122
|
-
from
|
|
123
|
+
from regscale.utils.version import RegscaleVersion
|
|
123
124
|
|
|
124
125
|
regscale_version = cls._get_api_handler().regscale_version
|
|
125
126
|
|
|
126
|
-
if len(regscale_version) >= 10 or
|
|
127
|
+
if len(regscale_version) >= 10 or RegscaleVersion.compare_versions(regscale_version, "6.13.0.0"):
|
|
127
128
|
return v
|
|
128
129
|
else:
|
|
129
130
|
return None
|
regscale/regscale.py
CHANGED
|
@@ -142,6 +142,11 @@ nist = import_command_with_timing(PUBLIC, "nist")
|
|
|
142
142
|
oscal = import_command_with_timing(PUBLIC, "oscal")
|
|
143
143
|
criticality_updater = import_command_with_timing(PUBLIC, "criticality_updater")
|
|
144
144
|
|
|
145
|
+
############################################################
|
|
146
|
+
# Utils
|
|
147
|
+
############################################################
|
|
148
|
+
cci_importer = import_command_with_timing("regscale.integrations.public.cci_importer", "cci_importer")
|
|
149
|
+
|
|
145
150
|
############################################################
|
|
146
151
|
# Commercial Integrations
|
|
147
152
|
############################################################
|
|
@@ -816,6 +821,8 @@ cli.add_command(emass) # add eMASS support
|
|
|
816
821
|
cli.add_command(fedramp) # add FedRAMP support
|
|
817
822
|
cli.add_command(nist) # add Nist_Catalog support
|
|
818
823
|
cli.add_command(oscal) # add OSCAL support
|
|
824
|
+
cli.add_command(criticality_updater) # add Criticality Updater support
|
|
825
|
+
cli.add_command(cci_importer) # add CCI Importer support
|
|
819
826
|
|
|
820
827
|
# start function for the CLI
|
|
821
828
|
if __name__ == "__main__":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: regscale-cli
|
|
3
|
-
Version: 6.
|
|
3
|
+
Version: 6.24.0.1
|
|
4
4
|
Summary: Command Line Interface (CLI) for bulk processing/loading data into RegScale
|
|
5
5
|
Home-page: https://github.com/RegScale/regscale-cli
|
|
6
6
|
Author: Travis Howerton
|
|
@@ -36,7 +36,7 @@ Requires-Dist: google-cloud-asset ~=3.22
|
|
|
36
36
|
Requires-Dist: google-cloud-securitycenter ~=1.25
|
|
37
37
|
Requires-Dist: gql ~=3.5.0
|
|
38
38
|
Requires-Dist: inflect
|
|
39
|
-
Requires-Dist: jira
|
|
39
|
+
Requires-Dist: jira >=3.8.0
|
|
40
40
|
Requires-Dist: jwcrypto >=1.5.1
|
|
41
41
|
Requires-Dist: lxml ==5.3.0
|
|
42
42
|
Requires-Dist: markdown
|
|
@@ -68,7 +68,7 @@ Requires-Dist: setuptools
|
|
|
68
68
|
Requires-Dist: simple-salesforce
|
|
69
69
|
Requires-Dist: smart-open ~=7.0
|
|
70
70
|
Requires-Dist: static
|
|
71
|
-
Requires-Dist: synqly
|
|
71
|
+
Requires-Dist: synqly ==0.4.56
|
|
72
72
|
Requires-Dist: tools
|
|
73
73
|
Requires-Dist: typing-extensions ~=4.12.2
|
|
74
74
|
Requires-Dist: wheel
|
|
@@ -105,7 +105,7 @@ Requires-Dist: greenlet >=3.0.3 ; extra == 'airflow'
|
|
|
105
105
|
Requires-Dist: importlib-metadata >=6.5 ; extra == 'airflow'
|
|
106
106
|
Requires-Dist: inflect ; extra == 'airflow'
|
|
107
107
|
Requires-Dist: jinja2 >=3.1.5 ; extra == 'airflow'
|
|
108
|
-
Requires-Dist: jira
|
|
108
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'airflow'
|
|
109
109
|
Requires-Dist: jmespath ==1.0.0 ; extra == 'airflow'
|
|
110
110
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'airflow'
|
|
111
111
|
Requires-Dist: lxml ==5.3.0 ; extra == 'airflow'
|
|
@@ -150,7 +150,7 @@ Requires-Dist: setuptools >=69.0.0 ; extra == 'airflow'
|
|
|
150
150
|
Requires-Dist: simple-salesforce ; extra == 'airflow'
|
|
151
151
|
Requires-Dist: smart-open ~=7.0 ; extra == 'airflow'
|
|
152
152
|
Requires-Dist: static ; extra == 'airflow'
|
|
153
|
-
Requires-Dist: synqly
|
|
153
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'airflow'
|
|
154
154
|
Requires-Dist: tools ; extra == 'airflow'
|
|
155
155
|
Requires-Dist: typing-extensions ~=4.12.2 ; extra == 'airflow'
|
|
156
156
|
Requires-Dist: wheel ; extra == 'airflow'
|
|
@@ -189,7 +189,7 @@ Requires-Dist: greenlet >=3.0.3 ; extra == 'airflow-azure'
|
|
|
189
189
|
Requires-Dist: importlib-metadata >=6.5 ; extra == 'airflow-azure'
|
|
190
190
|
Requires-Dist: inflect ; extra == 'airflow-azure'
|
|
191
191
|
Requires-Dist: jinja2 >=3.1.5 ; extra == 'airflow-azure'
|
|
192
|
-
Requires-Dist: jira
|
|
192
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'airflow-azure'
|
|
193
193
|
Requires-Dist: jmespath ==1.0.0 ; extra == 'airflow-azure'
|
|
194
194
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'airflow-azure'
|
|
195
195
|
Requires-Dist: lxml ==5.3.0 ; extra == 'airflow-azure'
|
|
@@ -235,7 +235,7 @@ Requires-Dist: setuptools >=69.0.0 ; extra == 'airflow-azure'
|
|
|
235
235
|
Requires-Dist: simple-salesforce ; extra == 'airflow-azure'
|
|
236
236
|
Requires-Dist: smart-open ~=7.0 ; extra == 'airflow-azure'
|
|
237
237
|
Requires-Dist: static ; extra == 'airflow-azure'
|
|
238
|
-
Requires-Dist: synqly
|
|
238
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'airflow-azure'
|
|
239
239
|
Requires-Dist: tools ; extra == 'airflow-azure'
|
|
240
240
|
Requires-Dist: typing-extensions ~=4.12.2 ; extra == 'airflow-azure'
|
|
241
241
|
Requires-Dist: wheel ; extra == 'airflow-azure'
|
|
@@ -273,7 +273,7 @@ Requires-Dist: greenlet >=3.0.3 ; extra == 'airflow-sqlserver'
|
|
|
273
273
|
Requires-Dist: importlib-metadata >=6.5 ; extra == 'airflow-sqlserver'
|
|
274
274
|
Requires-Dist: inflect ; extra == 'airflow-sqlserver'
|
|
275
275
|
Requires-Dist: jinja2 >=3.1.5 ; extra == 'airflow-sqlserver'
|
|
276
|
-
Requires-Dist: jira
|
|
276
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'airflow-sqlserver'
|
|
277
277
|
Requires-Dist: jmespath ==1.0.0 ; extra == 'airflow-sqlserver'
|
|
278
278
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'airflow-sqlserver'
|
|
279
279
|
Requires-Dist: lxml ==5.3.0 ; extra == 'airflow-sqlserver'
|
|
@@ -320,7 +320,7 @@ Requires-Dist: setuptools >=69.0.0 ; extra == 'airflow-sqlserver'
|
|
|
320
320
|
Requires-Dist: simple-salesforce ; extra == 'airflow-sqlserver'
|
|
321
321
|
Requires-Dist: smart-open ~=7.0 ; extra == 'airflow-sqlserver'
|
|
322
322
|
Requires-Dist: static ; extra == 'airflow-sqlserver'
|
|
323
|
-
Requires-Dist: synqly
|
|
323
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'airflow-sqlserver'
|
|
324
324
|
Requires-Dist: tools ; extra == 'airflow-sqlserver'
|
|
325
325
|
Requires-Dist: typing-extensions ~=4.12.2 ; extra == 'airflow-sqlserver'
|
|
326
326
|
Requires-Dist: wheel ; extra == 'airflow-sqlserver'
|
|
@@ -365,7 +365,7 @@ Requires-Dist: importlib-resources ; extra == 'all'
|
|
|
365
365
|
Requires-Dist: inflect ; extra == 'all'
|
|
366
366
|
Requires-Dist: jinja2 ; extra == 'all'
|
|
367
367
|
Requires-Dist: jinja2 >=3.1.5 ; extra == 'all'
|
|
368
|
-
Requires-Dist: jira
|
|
368
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'all'
|
|
369
369
|
Requires-Dist: jmespath ==1.0.0 ; extra == 'all'
|
|
370
370
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'all'
|
|
371
371
|
Requires-Dist: lxml ==5.3.0 ; extra == 'all'
|
|
@@ -410,7 +410,7 @@ Requires-Dist: setuptools >=69.0.0 ; extra == 'all'
|
|
|
410
410
|
Requires-Dist: simple-salesforce ; extra == 'all'
|
|
411
411
|
Requires-Dist: smart-open ~=7.0 ; extra == 'all'
|
|
412
412
|
Requires-Dist: static ; extra == 'all'
|
|
413
|
-
Requires-Dist: synqly
|
|
413
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'all'
|
|
414
414
|
Requires-Dist: tools ; extra == 'all'
|
|
415
415
|
Requires-Dist: typing-extensions ~=4.12.2 ; extra == 'all'
|
|
416
416
|
Requires-Dist: werkzeug >=2.3.8 ; extra == 'all'
|
|
@@ -440,7 +440,7 @@ Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'ansible'
|
|
|
440
440
|
Requires-Dist: gql ~=3.5.0 ; extra == 'ansible'
|
|
441
441
|
Requires-Dist: inflect ; extra == 'ansible'
|
|
442
442
|
Requires-Dist: jinja2 ; extra == 'ansible'
|
|
443
|
-
Requires-Dist: jira
|
|
443
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'ansible'
|
|
444
444
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'ansible'
|
|
445
445
|
Requires-Dist: lxml ==5.3.0 ; extra == 'ansible'
|
|
446
446
|
Requires-Dist: markdown ; extra == 'ansible'
|
|
@@ -472,7 +472,7 @@ Requires-Dist: setuptools ; extra == 'ansible'
|
|
|
472
472
|
Requires-Dist: simple-salesforce ; extra == 'ansible'
|
|
473
473
|
Requires-Dist: smart-open ~=7.0 ; extra == 'ansible'
|
|
474
474
|
Requires-Dist: static ; extra == 'ansible'
|
|
475
|
-
Requires-Dist: synqly
|
|
475
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'ansible'
|
|
476
476
|
Requires-Dist: tools ; extra == 'ansible'
|
|
477
477
|
Requires-Dist: typing-extensions ~=4.12.2 ; extra == 'ansible'
|
|
478
478
|
Requires-Dist: wheel ; extra == 'ansible'
|
|
@@ -506,7 +506,7 @@ Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'dev'
|
|
|
506
506
|
Requires-Dist: gql ~=3.5.0 ; extra == 'dev'
|
|
507
507
|
Requires-Dist: inflect ; extra == 'dev'
|
|
508
508
|
Requires-Dist: isort ; extra == 'dev'
|
|
509
|
-
Requires-Dist: jira
|
|
509
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'dev'
|
|
510
510
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'dev'
|
|
511
511
|
Requires-Dist: lxml-stubs ; extra == 'dev'
|
|
512
512
|
Requires-Dist: lxml ==5.3.0 ; extra == 'dev'
|
|
@@ -555,7 +555,7 @@ Requires-Dist: sphinx ; extra == 'dev'
|
|
|
555
555
|
Requires-Dist: sphinx-autodoc-typehints ; extra == 'dev'
|
|
556
556
|
Requires-Dist: sphinx-click ; extra == 'dev'
|
|
557
557
|
Requires-Dist: static ; extra == 'dev'
|
|
558
|
-
Requires-Dist: synqly
|
|
558
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'dev'
|
|
559
559
|
Requires-Dist: tools ; extra == 'dev'
|
|
560
560
|
Requires-Dist: types-python-dateutil ; extra == 'dev'
|
|
561
561
|
Requires-Dist: types-pyyaml ; extra == 'dev'
|
|
@@ -592,7 +592,7 @@ Requires-Dist: google-cloud-securitycenter ~=1.25 ; extra == 'server'
|
|
|
592
592
|
Requires-Dist: gql ~=3.5.0 ; extra == 'server'
|
|
593
593
|
Requires-Dist: importlib-resources ; extra == 'server'
|
|
594
594
|
Requires-Dist: inflect ; extra == 'server'
|
|
595
|
-
Requires-Dist: jira
|
|
595
|
+
Requires-Dist: jira >=3.8.0 ; extra == 'server'
|
|
596
596
|
Requires-Dist: jwcrypto >=1.5.1 ; extra == 'server'
|
|
597
597
|
Requires-Dist: lxml ==5.3.0 ; extra == 'server'
|
|
598
598
|
Requires-Dist: markdown ; extra == 'server'
|
|
@@ -624,7 +624,7 @@ Requires-Dist: setuptools ; extra == 'server'
|
|
|
624
624
|
Requires-Dist: simple-salesforce ; extra == 'server'
|
|
625
625
|
Requires-Dist: smart-open ~=7.0 ; extra == 'server'
|
|
626
626
|
Requires-Dist: static ; extra == 'server'
|
|
627
|
-
Requires-Dist: synqly
|
|
627
|
+
Requires-Dist: synqly ==0.4.56 ; extra == 'server'
|
|
628
628
|
Requires-Dist: tools ; extra == 'server'
|
|
629
629
|
Requires-Dist: typing-extensions ~=4.12.2 ; extra == 'server'
|
|
630
630
|
Requires-Dist: werkzeug >=2.3.8 ; extra == 'server'
|