regscale-cli 6.20.4.1__py3-none-any.whl → 6.20.6.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.

Files changed (49) hide show
  1. regscale/__init__.py +1 -1
  2. regscale/_version.py +39 -0
  3. regscale/core/app/internal/__init__.py +13 -0
  4. regscale/core/app/internal/model_editor.py +3 -3
  5. regscale/core/app/internal/set_permissions.py +173 -0
  6. regscale/core/app/utils/file_utils.py +11 -1
  7. regscale/core/app/utils/regscale_utils.py +34 -129
  8. regscale/core/utils/date.py +86 -30
  9. regscale/integrations/commercial/defender.py +3 -0
  10. regscale/integrations/commercial/qualys/__init__.py +40 -14
  11. regscale/integrations/commercial/qualys/containers.py +324 -0
  12. regscale/integrations/commercial/qualys/scanner.py +203 -8
  13. regscale/integrations/commercial/synqly/edr.py +10 -0
  14. regscale/integrations/commercial/wizv2/click.py +11 -7
  15. regscale/integrations/commercial/wizv2/constants.py +28 -0
  16. regscale/integrations/commercial/wizv2/issue.py +3 -2
  17. regscale/integrations/commercial/wizv2/parsers.py +23 -0
  18. regscale/integrations/commercial/wizv2/scanner.py +89 -30
  19. regscale/integrations/commercial/wizv2/utils.py +208 -75
  20. regscale/integrations/commercial/wizv2/variables.py +2 -1
  21. regscale/integrations/commercial/wizv2/wiz_auth.py +3 -3
  22. regscale/integrations/public/fedramp/fedramp_cis_crm.py +98 -20
  23. regscale/integrations/public/fedramp/fedramp_docx.py +2 -3
  24. regscale/integrations/scanner_integration.py +7 -2
  25. regscale/models/integration_models/cisa_kev_data.json +187 -5
  26. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  27. regscale/models/regscale_models/__init__.py +2 -0
  28. regscale/models/regscale_models/asset.py +1 -1
  29. regscale/models/regscale_models/catalog.py +16 -0
  30. regscale/models/regscale_models/file.py +2 -1
  31. regscale/models/regscale_models/form_field_value.py +59 -1
  32. regscale/models/regscale_models/issue.py +47 -0
  33. regscale/models/regscale_models/modules.py +88 -1
  34. regscale/models/regscale_models/organization.py +30 -0
  35. regscale/models/regscale_models/regscale_model.py +20 -6
  36. regscale/models/regscale_models/security_control.py +47 -0
  37. regscale/models/regscale_models/security_plan.py +32 -0
  38. regscale/models/regscale_models/vulnerability.py +3 -3
  39. regscale/models/regscale_models/vulnerability_mapping.py +2 -2
  40. regscale/regscale.py +2 -0
  41. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/METADATA +1 -1
  42. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/RECORD +49 -44
  43. tests/fixtures/test_fixture.py +33 -4
  44. tests/regscale/core/test_app.py +53 -32
  45. tests/regscale/test_init.py +94 -0
  46. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/LICENSE +0 -0
  47. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/WHEEL +0 -0
  48. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/entry_points.txt +0 -0
  49. {regscale_cli-6.20.4.1.dist-info → regscale_cli-6.20.6.0.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,12 @@
1
1
  """RegScale models."""
2
2
 
3
+ from .assessment_plan import *
3
4
  from .assessment import *
4
5
  from .asset import *
5
6
  from .asset_mapping import *
6
7
  from .business_impact_assessment import *
7
8
  from .catalog import *
9
+ from .case import *
8
10
  from .cci import *
9
11
  from .change import *
10
12
  from .checklist import *
@@ -8,7 +8,7 @@ from enum import Enum
8
8
  from typing import Any, List, Optional, Union, cast
9
9
  from urllib.parse import urljoin
10
10
 
11
- from pydantic import ConfigDict, Field
11
+ from pydantic import Field
12
12
  from requests import Response
13
13
  from rich.progress import Progress
14
14
 
@@ -259,3 +259,19 @@ class Catalog(RegScaleModel):
259
259
  return response
260
260
  cls.log_response_error(response, suppress_error=True)
261
261
  return response
262
+
263
+ @classmethod
264
+ def find_by_guid(cls, guid: str) -> Optional["Catalog"]:
265
+ """
266
+ Find a catalog by its GUID.
267
+
268
+ :param str guid: The GUID of the catalog
269
+ :return: The catalog object or None if not found
270
+ :rtype: Optional["Catalog"]
271
+ """
272
+ endpoint = cls.get_endpoint("find_by_guid").format(model_slug=cls.get_module_slug(), strID=guid)
273
+ response = cls._get_api_handler().get(endpoint)
274
+
275
+ if response and response.ok and response.status_code not in [204, 404]:
276
+ return cls.model_validate(response.json())
277
+ return None
@@ -188,10 +188,11 @@ class File(BaseModel):
188
188
  file_res = api.post(
189
189
  url=f"{api.config['domain']}/api/files",
190
190
  headers=file_headers,
191
- json=new_file.dict(),
191
+ json=new_file.model_dump(),
192
192
  )
193
193
  if file_res.ok and return_object:
194
194
  return File(**file_res.json()).model_dump()
195
+ File.create_tag_mappings(file_res)
195
196
  else:
196
197
  return False
197
198
  return file_res.ok
@@ -8,6 +8,7 @@ from typing import Any, List, Optional, Dict
8
8
  from pydantic import ConfigDict, Field
9
9
 
10
10
  from regscale.models.regscale_models.regscale_model import RegScaleModel
11
+ from regscale.models.regscale_models.module import Module
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -79,7 +80,7 @@ class FormFieldValue(RegScaleModel):
79
80
  Get custom data for a record
80
81
  :param record_id: record id
81
82
  :param module_name: module name
82
- :param form_id: form id
83
+ :param form_id: form tab id
83
84
  :return: list of custom fields
84
85
  """
85
86
  result = cls._get_api_handler().get(
@@ -92,3 +93,60 @@ class FormFieldValue(RegScaleModel):
92
93
  else:
93
94
  cls.log_response_error(response=result)
94
95
  return []
96
+
97
+ @staticmethod
98
+ def check_custom_fields(fields_list: list, module_name: str, tab_name: str) -> dict:
99
+ """
100
+ Check if the custom fields exist and
101
+ create if not
102
+
103
+ :param list fields_list: list list of custom fields
104
+ :param str module_name: name of the module in RegScale
105
+ :param str tab_name: name of the tab in the module
106
+ :return: map of custom fields names to ids
107
+ :return_type: dict
108
+ """
109
+ # Check the custom fields exist in RegScale
110
+ custom_form_fields = Module.get_form_fields_by_tab_id(module_name=module_name, tab_name=tab_name)
111
+ if custom_form_fields:
112
+ custom_form_field_id_dict = {cf.regScaleName: cf.id for cf in custom_form_fields}
113
+ else:
114
+ custom_form_field_id_dict = {}
115
+ missing_custom_fields = []
116
+ for field in fields_list:
117
+ if field not in custom_form_field_id_dict.keys():
118
+ missing_custom_fields.append(field)
119
+
120
+ if len(missing_custom_fields) > 0:
121
+ logger.error(
122
+ f"The following custom fields are missing:\n \
123
+ {missing_custom_fields}\n \
124
+ Load these custom fields in RegScale \
125
+ using the file: security_plan_custom_fields.json\
126
+ and run this command again"
127
+ )
128
+
129
+ return custom_form_field_id_dict
130
+
131
+ @staticmethod
132
+ def save_custom_fields(form_field_values: list):
133
+ """
134
+ Populate Custom Fields form a list of dict of
135
+ record_id, form_field_id, and field_value
136
+
137
+ :param list form_field_values: list of custom form
138
+ fields, values, and the record to which to post
139
+ :return: None
140
+ """
141
+ logger.debug("Creating custom form field values...")
142
+ # FormFieldValue class will throw errors if encountered
143
+ for form_field_value in form_field_values:
144
+ data = [
145
+ FormFieldValue(
146
+ formFieldId=form_field_value["form_field_id"], fieldValue=form_field_value["field_value"]
147
+ ),
148
+ ]
149
+ if data:
150
+ FormFieldValue.save_custom_data(
151
+ record_id=form_field_value["record_id"], module_name="securityplans", data=data
152
+ )
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  """Model for a RegScale Issue"""
4
+ import datetime
4
5
  from collections import defaultdict
5
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
6
7
  from enum import Enum
@@ -387,6 +388,52 @@ class Issue(RegScaleModel):
387
388
  return "High"
388
389
  return ""
389
390
 
391
+ @staticmethod
392
+ def get_due_date(
393
+ severity: Union[IssueSeverity, str],
394
+ config: dict,
395
+ key: str,
396
+ start_date: Union[str, datetime.datetime, None] = None,
397
+ dt_format: Optional[str] = "%Y-%m-%dT%H:%M:%S",
398
+ ) -> str:
399
+ """
400
+ Function to return due date based on the severity of the issue; the values are in the init.yaml
401
+ and if not, it will default to 60 days from the current date.
402
+
403
+ :param Union[IssueSeverity, str] severity: Severity of the issue, can be an IssueSeverity enum or a string
404
+ :param dict config: Application config
405
+ :param str key: The key to use for init.yaml from the issues section to determine the due date
406
+ :param Union[str, datetime.datetime, None] start_date: The date to start the due date calculation from, defaults to current date
407
+ :param Optional[str] dt_format: String of the date format to use, defaults to "%Y-%m-%dT%H:%M:%S"
408
+ :return: Due date for the issue
409
+ :rtype: str
410
+ """
411
+ if isinstance(start_date, str):
412
+ from regscale.core.utils.date import datetime_obj
413
+
414
+ start_date = datetime_obj(start_date)
415
+ elif start_date is None:
416
+ start_date = datetime.datetime.now()
417
+
418
+ # if the severity is an enum, get the value
419
+ if isinstance(severity, IssueSeverity):
420
+ severity = severity.value
421
+ elif severity.lower() not in [severity.value.lower() for severity in IssueSeverity]:
422
+ severity = IssueSeverity.NotAssigned.value
423
+
424
+ if severity == IssueSeverity.Critical.value:
425
+ days = config["issues"].get(key, {}).get("critical", 0)
426
+ elif severity == IssueSeverity.High.value:
427
+ days = config["issues"].get(key, {}).get("high", 0)
428
+ elif severity == IssueSeverity.Moderate.value:
429
+ days = config["issues"].get(key, {}).get("moderate", 0) or config["issues"].get(key, {}).get("medium", 0)
430
+ elif severity == IssueSeverity.Low.value:
431
+ days = config["issues"].get(key, {}).get("low", 0)
432
+ else:
433
+ days = 60
434
+ due_date = start_date + datetime.timedelta(days=days)
435
+ return due_date.strftime(dt_format)
436
+
390
437
  @staticmethod
391
438
  def assign_severity(value: Optional[Any] = None) -> str:
392
439
  """
@@ -4,7 +4,7 @@
4
4
 
5
5
  # standard python imports
6
6
  from dataclasses import asdict, dataclass
7
- from typing import Any
7
+ from typing import Any, Optional
8
8
 
9
9
  from rich.console import Console
10
10
  from rich.table import Table
@@ -189,3 +189,90 @@ class Modules:
189
189
 
190
190
  # return the string
191
191
  return output
192
+
193
+ @staticmethod
194
+ def module_to_class(module: str) -> Optional[Any]:
195
+ """
196
+ Function to convert RegScale module to RegScale class
197
+
198
+ :param str module: RegScale module
199
+ :return: RegScale class if found in the mapping, else None
200
+ :rtype: Optional[Any]
201
+ """
202
+ from regscale.models import (
203
+ AssessmentPlan,
204
+ Assessment,
205
+ Asset,
206
+ Case,
207
+ Catalog,
208
+ Change,
209
+ Component,
210
+ Evidence,
211
+ Incident,
212
+ Issue,
213
+ Policy,
214
+ Project,
215
+ Questionnaires,
216
+ Requirement,
217
+ Risk,
218
+ SecurityPlan,
219
+ SupplyChain,
220
+ Task,
221
+ Threat,
222
+ )
223
+
224
+ regscale_models = {
225
+ "assessmentplans": AssessmentPlan,
226
+ "assessments": Assessment,
227
+ "assets": Asset,
228
+ "cases": Case,
229
+ "catalogues": Catalog,
230
+ "changes": Change,
231
+ "components": Component,
232
+ "evidence": Evidence,
233
+ "incidents": Incident,
234
+ "issues": Issue,
235
+ "policies": Policy,
236
+ "projects": Project,
237
+ "questionnaires": Questionnaires,
238
+ "requirements": Requirement,
239
+ "risks": Risk,
240
+ "securityplans": SecurityPlan,
241
+ "supplychain": SupplyChain,
242
+ "tasks": Task,
243
+ "threats": Threat,
244
+ }
245
+
246
+ return regscale_models.get(module) or None
247
+
248
+ @staticmethod
249
+ def get_module_to_id(module: str) -> Optional[int]:
250
+ """
251
+ Returns the id of a RegScale module
252
+
253
+ :return: id of RegScale module, if found in the mapping, else None
254
+ :rtype: Optional[int]
255
+ """
256
+ module_to_id = {
257
+ "assessmentplans": 33,
258
+ "assessments": 2,
259
+ "assets": 3,
260
+ "cases": 28,
261
+ "catalogues": 4,
262
+ "changes": 31,
263
+ "components": 27,
264
+ "evidence": 32,
265
+ "incidents": 8,
266
+ "issues": 10,
267
+ "policies": 11,
268
+ "projects": 12,
269
+ "questionnaires": 26,
270
+ "requirements": 13,
271
+ "risks": 14,
272
+ "securityplans": 16,
273
+ "supplychain": 25,
274
+ "tasks": 18,
275
+ "threats": 19,
276
+ }
277
+
278
+ return module_to_id.get(module)
@@ -0,0 +1,30 @@
1
+ """Organization model for RegScale."""
2
+
3
+ from typing import Optional
4
+
5
+ from pydantic import ConfigDict
6
+
7
+ from regscale.models.regscale_models.regscale_model import RegScaleModel
8
+
9
+
10
+ class Organization(RegScaleModel):
11
+ """Data Model for Organizations"""
12
+
13
+ _module_slug = "organizations"
14
+
15
+ id: int = 0
16
+ name: Optional[str] = ""
17
+ description: Optional[str] = ""
18
+ status: Optional[str] = "Active"
19
+
20
+ @staticmethod
21
+ def _get_additional_endpoints() -> dict:
22
+ """
23
+ Get additional endpoints for the Facility model.
24
+
25
+ :return: A dictionary of additional endpoints
26
+ :rtype: dict
27
+ """
28
+ return ConfigDict(
29
+ get_list="/api/{model_slug}/getList",
30
+ )
@@ -410,12 +410,12 @@ class RegScaleModel(BaseModel, ABC):
410
410
  """
411
411
  hidden_fields = set(
412
412
  attribute_name
413
- for attribute_name, model_field in self.model_fields.items()
413
+ for attribute_name, model_field in self.__class__.model_fields.items()
414
414
  if model_field.from_field("hidden") == "hidden"
415
415
  )
416
416
  unset_fields = set(
417
417
  attribute_name
418
- for attribute_name, model_field in self.model_fields.items()
418
+ for attribute_name, model_field in self.__class__.model_fields.items()
419
419
  if getattr(self, attribute_name, None) is None
420
420
  )
421
421
  excluded_fields = hidden_fields.union(unset_fields)
@@ -963,24 +963,32 @@ class RegScaleModel(BaseModel, ABC):
963
963
  return items
964
964
 
965
965
  @classmethod
966
- def get_field_names(cls) -> List[str]:
966
+ def get_field_names(cls, use_aliases: bool = False) -> List[str]:
967
967
  """
968
968
  Get the field names for the Asset model.
969
969
 
970
+ :param bool use_aliases: Whether to use aliases for the field names, defaults to False
970
971
  :return: List of field names
971
972
  :rtype: List[str]
972
973
  """
974
+ if use_aliases:
975
+ return [val.alias or key for key, val in cls.model_fields.items() if not key.startswith("_")]
973
976
  return [x for x in get_type_hints(cls).keys() if not x.startswith("_")]
974
977
 
975
978
  @classmethod
976
- def build_graphql_fields(cls) -> str:
979
+ def build_graphql_fields(cls, use_aliases: bool = False) -> str:
977
980
  """
978
981
  Dynamically builds a GraphQL query for a given Pydantic model class.
979
982
 
983
+ :param bool use_aliases: Whether to use aliases for the field names, defaults to False
980
984
  :return: A string representing the GraphQL query
981
985
  :rtype: str
982
986
  """
983
- return "\n".join(x for x in cls.get_field_names() if x not in cls._exclude_graphql_fields and x != "extra_data")
987
+ return "\n".join(
988
+ x
989
+ for x in cls.get_field_names(use_aliases=use_aliases)
990
+ if x not in cls._exclude_graphql_fields and x != "extra_data"
991
+ )
984
992
 
985
993
  @classmethod
986
994
  def get_by_parent(cls, parent_id: int, parent_module: str) -> List[T]:
@@ -1142,13 +1150,18 @@ class RegScaleModel(BaseModel, ABC):
1142
1150
  :return: The saved object
1143
1151
  :rtype: T
1144
1152
  """
1145
- if self.has_changed():
1153
+ # Check if the model has change tracking and if there are changes
1154
+ has_change_tracking = hasattr(self, "has_changed") and callable(getattr(self, "has_changed", None))
1155
+ should_save = not has_change_tracking or self.has_changed()
1156
+
1157
+ if should_save:
1146
1158
  if bulk:
1147
1159
  logger.debug(f"Adding {self.__class__.__name__} {self.id} to pending updates")
1148
1160
  self._get_pending_updates().add(self._get_cache_key(self))
1149
1161
  self.cache_object(self) # Update the cache with the current state
1150
1162
  return self
1151
1163
  else:
1164
+ logger.debug(f"Saving {self.__class__.__name__} {self.id}")
1152
1165
  return self._perform_save()
1153
1166
  else:
1154
1167
  logger.debug(f"No changes detected for {self.__class__.__name__} {self.id}")
@@ -1276,6 +1289,7 @@ class RegScaleModel(BaseModel, ABC):
1276
1289
  endpoint = self.get_endpoint("update").format(id=self.id)
1277
1290
  response = self._get_api_handler().put(endpoint=endpoint, data=self.dict(), headers=self._get_headers())
1278
1291
  if hasattr(response, "ok") and response.ok:
1292
+ logger.debug(f"Successfully saved {self.__class__.__name__} {self.id}")
1279
1293
  obj = self.__class__(**response.json())
1280
1294
  self.cache_object(obj)
1281
1295
  return obj
@@ -24,8 +24,10 @@ class SecurityControl(RegScaleModel):
24
24
  ["controlId", "catalogueId"],
25
25
  ]
26
26
  _parent_id_field = "catalogueId"
27
+ _exclude_graphql_fields = ["objectives", "tests", "parameters"]
27
28
 
28
29
  id: int = 0
30
+ otherId: Optional[str] = None
29
31
  isPublic: bool = True
30
32
  uuid: Optional[str] = None
31
33
  controlId: Optional[str] = None
@@ -130,3 +132,48 @@ class SecurityControl(RegScaleModel):
130
132
  config = api.config
131
133
  res = api.get(config["domain"] + f"/api/securitycontrols/findByUniqueId/{control_name}/{catalog_id}")
132
134
  return SecurityControl(**res.json()) if res.status_code == 200 else None
135
+
136
+ @classmethod
137
+ def get_controls_by_parent_id_and_module(
138
+ cls, parent_id: int, parent_module: str, return_dicts: bool = False
139
+ ) -> List["SecurityControl"]:
140
+ """
141
+ Get a list of Security Controls by parent ID and module using GraphQL
142
+
143
+ :param int parent_id: The ID of the parent
144
+ :param str parent_module: The module of the parent
145
+ :param bool return_dicts: Whether to return the controls as a list of dicts, defaults to False
146
+ :return: A list of Security Controls
147
+ :rtype: List["SecurityControl"]
148
+ """
149
+ query = f"""
150
+ query {{
151
+ controlImplementations(skip: 0, take: 50, where: {{
152
+ parentId: {{
153
+ eq: {parent_id}
154
+ }},
155
+ parentModule: {{
156
+ eq: "{parent_module}"
157
+ }}
158
+ control: {{
159
+ id: {{
160
+ gt: 0
161
+ }}
162
+ }}
163
+ }}) {{
164
+ items {{
165
+ control {{
166
+ {cls.build_graphql_fields(use_aliases=True)}
167
+ }}
168
+ }}
169
+ totalCount
170
+ pageInfo {{
171
+ hasNextPage
172
+ }}
173
+ }}
174
+ }}"""
175
+ data = cls._get_api_handler().graph(query=query)
176
+ controls = data.get("controlImplementations", {}).get("items", [])
177
+ if return_dicts:
178
+ return [control["control"] for control in controls if control.get("control")]
179
+ return [cls(**control["control"]) for control in controls if control.get("control")]
@@ -27,6 +27,7 @@ class SecurityPlan(RegScaleModel):
27
27
  defaultAssessmentDays: int = 0
28
28
  planInformationSystemSecurityOfficerId: Optional[str] = None
29
29
  planAuthorizingOfficialId: Optional[str] = None
30
+ systemSecurityManagerId: Optional[str] = None
30
31
  systemOwnerId: Optional[str] = None # this could be userID
31
32
  otherIdentifier: Optional[str] = ""
32
33
  confidentiality: str = ""
@@ -138,6 +139,8 @@ class SecurityPlan(RegScaleModel):
138
139
  return ConfigDict( # type: ignore
139
140
  get_control_implementations="/api/controlImplementation/getAllByPlan/{plan_id}",
140
141
  mega_api="/api/{module_slug}/megaAPI/{intId}",
142
+ export_cis_crm="/api/{module_slug}/exportFedrampRev5CisCrm/{intId}",
143
+ list="/api/{module_slug}/getList",
141
144
  )
142
145
 
143
146
  @classmethod
@@ -156,6 +159,35 @@ class SecurityPlan(RegScaleModel):
156
159
  return response.json()
157
160
  return {}
158
161
 
162
+ @classmethod
163
+ def export_cis_crm(cls, ssp_id: int) -> dict:
164
+ """
165
+ Export to a new CIS/CRM workbook with an existing SSP ID
166
+
167
+ :param int ssp_id: RegScale SSP ID
168
+ :return: A status message
169
+ :rtype: dict
170
+ """
171
+ response = cls._get_api_handler().get(
172
+ endpoint=cls.get_endpoint("export_cis_crm").format(module_slug=cls._module_slug, intId=ssp_id)
173
+ )
174
+ if response.ok:
175
+ return response.json()
176
+ return {}
177
+
178
+ @classmethod
179
+ def get_list(cls) -> list:
180
+ """
181
+ Get a list of objects.
182
+
183
+ :return: A list of objects
184
+ :rtype: list
185
+ """
186
+ response = cls._get_api_handler().get(endpoint=cls.get_endpoint("list").format(module_slug=cls._module_slug))
187
+ if not response.raise_for_status():
188
+ return response.json()
189
+ return []
190
+
159
191
  # Legacy code
160
192
  def create_new_ssp(self, api: Api, return_id: Optional[bool] = False) -> Union[int, None, "SecurityPlan"]:
161
193
  """
@@ -14,9 +14,9 @@ from regscale.core.app.api import Api
14
14
  from regscale.core.app.application import Application
15
15
  from regscale.core.app.utils.app_utils import get_current_datetime
16
16
  from regscale.models import regscale_models
17
+ from regscale.models.regscale_models.issue import IssueStatus
17
18
  from regscale.models.regscale_models.regscale_model import RegScaleModel
18
19
 
19
-
20
20
  logger = logging.getLogger("regscale")
21
21
 
22
22
 
@@ -68,7 +68,7 @@ class Vulnerability(RegScaleModel):
68
68
  firstSeen: Optional[str] = None
69
69
  daysOpen: Optional[int] = None
70
70
  dns: Optional[str] = None
71
- ipAddress: Optional[str] = ""
71
+ ipAddress: Optional[str] = None
72
72
  mitigated: Optional[bool] = None
73
73
  operatingSystem: Optional[str] = None
74
74
  port: Optional[Union[str, int]] = None
@@ -86,7 +86,7 @@ class Vulnerability(RegScaleModel):
86
86
  tenantsId: int = Field(default=0)
87
87
  isPublic: bool = Field(default=False)
88
88
  dateClosed: Optional[str] = None
89
- status: Optional[VulnerabilityStatus] = VulnerabilityStatus.Open
89
+ status: Optional[Union[str, IssueStatus]] = Field(default_factory=IssueStatus.Open)
90
90
  buildVersion: Optional[str] = None
91
91
  cvsSv3BaseVector: Optional[str] = None
92
92
  cvsSv2BaseVector: Optional[str] = None
@@ -23,7 +23,7 @@ class VulnerabilityMapping(RegScaleModel):
23
23
  vulnerabilityId: int
24
24
  assetId: int
25
25
  scanId: Optional[int] = None
26
- securityPlansId: Optional[int] = None
26
+ securityPlanId: Optional[int] = None
27
27
  status: Optional[str] = Field(max_length=450)
28
28
  firstSeen: str
29
29
  lastSeen: str
@@ -33,7 +33,7 @@ class VulnerabilityMapping(RegScaleModel):
33
33
  lastUpdatedById: str = Field(default_factory=RegScaleModel.get_user_id)
34
34
  dateCreated: str
35
35
  dateLastUpdated: Optional[str] = None
36
- tenantsId: int = Field(default=0)
36
+ tenantsId: int = Field(default=1)
37
37
 
38
38
  @staticmethod
39
39
  def _get_additional_endpoints() -> ConfigDict:
regscale/regscale.py CHANGED
@@ -127,6 +127,7 @@ file_upload = import_command_with_timing("regscale.core.app.internal.file_upload
127
127
  migrations = import_command_with_timing(INTERNAL, "migrations")
128
128
  model = import_command_with_timing(INTERNAL, "model")
129
129
  issues = import_command_with_timing(INTERNAL, "issues")
130
+ set_permissions = import_command_with_timing(INTERNAL, "set_permissions")
130
131
 
131
132
  ############################################################
132
133
  # Public Integrations
@@ -757,6 +758,7 @@ cli.add_command(evidence) # add Evidence Feature
757
758
  cli.add_command(migrations) # add data migration support
758
759
  cli.add_command(issues) # add POAM(Issues) Editor Feature
759
760
  cli.add_command(model) # add POAM(Issues) Editor Feature
761
+ cli.add_command(set_permissions) # add builk editor for record permissions
760
762
 
761
763
  ############################################################
762
764
  # Add Commercial Integrations
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: regscale-cli
3
- Version: 6.20.4.1
3
+ Version: 6.20.6.0
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