Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Corrected timestamps and reverted float casting. #294 #292 #295

Merged
merged 4 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,25 @@ readme = {file = ["README.md"], content-type = "text/markdown"}
[tool.setuptools.packages.find]
include = ["tenb2jira*"]

[tool.ruff]
target-version = "py312"
exclude = [
".nova",
".github",
".git",
".pytest_cache",
"__pycache__"
]

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "B"]
fixable = [ "ALL" ]
unfixable = [ "B" ]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402"]
"**/{tests,docs,tools}/*" = ["E402"]

[tool.flake8]
max-line-length = 88
count = true
15 changes: 14 additions & 1 deletion tenb2jira/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,26 @@ def sync(configfile: Path,
verbose: bool = False,
cleanup: bool = True,
ignore_last_run: bool = False,
debug: bool = False,
):
"""
Perform the sync between Tenable & Jira
"""
setup_logging(verbose)
with configfile.open('r', encoding='utf-8') as fobj:
config = tomlkit.load(fobj)

if debug:
verbose = True
cleanup = False
config['jira']['max_workers'] = 1

setup_logging(verbose)

dbfile = Path(config['mapping_database']['path'])
if dbfile.exists():
console.print('WARNING :: Mapping Cache discovered. We will be removing it.')
dbfile.unlink()

processor = Processor(config, ignore_last_run=ignore_last_run)
console.print(Columns([tenable_table(config),
jira_table(config)
Expand Down
13 changes: 8 additions & 5 deletions tenb2jira/jira/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,15 @@ def __repr__(self):

@property
def attr(self):
if self.attribute:
return self.attribute
if self.static_value:
return self.attribute
"""
Return the appropriate value (either platform_id, static_value, or
attribute) depending on how the field was configured.
"""
if self.platform_id:
return self.platform_id
if self.static_value:
return self.static_value
return self.attribute

def fetch_field_id(self, api) -> bool:
"""
Expand Down Expand Up @@ -155,7 +158,7 @@ def parse_value(self, finding: dict) -> Any:

# float values should always be returned as a float.
case 'float':
return str(float(value))
return float(value)

# datetime values should be returned in a specific format. Here
# we attempt to normalize both timestamp and ISO formatted values
Expand Down
16 changes: 11 additions & 5 deletions tenb2jira/jira/jira.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any
from typing import Any, Dict
import time
import logging
from box import Box
Expand All @@ -24,12 +24,18 @@ class Jira:
project: dict

@property
def field_by_name_map(self):
return {f.name:f for f in self.fields}
def field_by_name_map(self) -> Dict[str, Field]:
"""
Returns the fields in a dictionary with the field name as the key
"""
return {f.name: f for f in self.fields}

@property
def field_by_id_map(self):
return {f.id:f for f in self.fields}
def field_by_id_map(self) -> Dict[str, Field]:
"""
Returns the fields in a dictionary with the field id as the key
"""
return {f.id: f for f in self.fields}

def __init__(self, config: dict):
self.config = config
Expand Down
2 changes: 1 addition & 1 deletion tenb2jira/jira/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def __init__(self, issue_def: "Task", is_open: bool = True):
self.is_open = is_open

def __repr__(self):
return f'Task("{self.jql}", {len(self.fields)})'
return f'Task("{self.jql_stmt}", {len(self.fields)})'

@property
def jql_stmt(self):
Expand Down
35 changes: 20 additions & 15 deletions tenb2jira/processor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from typing import Optional
import logging
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
Expand Down Expand Up @@ -29,8 +30,13 @@ class Processor:
plugin_id: str
closed_map: list[str]

def __init__(self, config: dict, ignore_last_run: bool = False):
dburi = f'sqlite:///{config["mapping_database"].get("path")}'
def __init__(self,
config: dict,
ignore_last_run: bool = False,
dburi: Optional[str] = None,
):
if not dburi:
dburi = f'sqlite:///{config["mapping_database"].get("path")}'

# For situations where we will need to ignore the last_run variable,
# This will pull it from the config, forcing the integration to use
Expand Down Expand Up @@ -124,7 +130,7 @@ def build_mapping_db_model(self,
value = value[0]
item[fields[key]] = value
# item = {fields[k]: v for k, v in issue.fields.items()}
item['updated'] = self.start_time
item['updated'] = self.start_time.datetime
item['jira_id'] = issue.key
if not missing:
log.debug(f'Adding {issue.key} to cache.')
Expand Down Expand Up @@ -185,11 +191,11 @@ def upsert_task(self, s: Session, finding: dict) -> (int | None):
# and return the jira issue id back to the caller.
if sql:
if finding.get('integration_pid_updated') > self.last_run:
if sql.updated <= self.start_time:
if arrow.get(sql.updated, 'UTC') <= self.start_time:
self.jira.api.issues.update(sql.jira_id,
fields=task.fields,
)
sql.updated = datetime.now()
sql.updated = datetime.utcnow()
s.commit()
log.info(f'Matched Task "{sql.jira_id}" to '
'SQL Cache and updated.')
Expand All @@ -213,7 +219,7 @@ def upsert_task(self, s: Session, finding: dict) -> (int | None):
if len(page.issues) == 1:
sql = TaskMap(plugin_id=task.fields[self.plugin_id],
jira_id=page.issues[0].key,
updated=datetime.now(),
updated=datetime.utcnow(),
)
s.add(sql)
s.commit()
Expand All @@ -232,7 +238,7 @@ def upsert_task(self, s: Session, finding: dict) -> (int | None):
resp = self.jira.api.issues.create(fields=task.fields)
sql = TaskMap(plugin_id=task.fields[self.plugin_id],
jira_id=resp.key,
updated=datetime.now()
updated=datetime.utcnow()
)
s.add(sql)
s.commit()
Expand Down Expand Up @@ -271,7 +277,7 @@ def upsert_subtask(self,
if sql:
if not task.is_open:
sql.is_open = task.is_open
sql.updated = datetime.now()
sql.updated = datetime.utcnow()
s.commit()
self.close_task(sql.jira_id)
action = 'closed subtask'
Expand Down Expand Up @@ -313,7 +319,7 @@ def upsert_subtask(self,
finding_id=task.fields[self.finding_id],
jira_id=page.issues[0].key,
is_open=task.is_open,
updated=datetime.now(),
updated=datetime.utcnow(),
)
s.add(sql)
s.commit()
Expand Down Expand Up @@ -341,7 +347,7 @@ def upsert_subtask(self,
finding_id=task.fields[self.finding_id],
jira_id=resp.key,
is_open=task.is_open,
updated=datetime.now(),
updated=datetime.utcnow(),
)
s.add(sql)
s.commit()
Expand Down Expand Up @@ -419,11 +425,10 @@ def sync(self, cleanup: bool = True):
"""
Tenable to Jira Synchronization method.
"""
self.start_time = datetime.now()
ts = int(arrow.get(self.start_time).timestamp())
self.start_time = arrow.utcnow()

# Get the findings and the asset cleanup generators.
findings = self.tenable.get_generator()
findings = self.tenable.get_generator(self.start_time)
asset_cleanup = self.tenable.get_asset_cleanup()

# build the db cache
Expand Down Expand Up @@ -469,8 +474,8 @@ def sync(self, cleanup: bool = True):
self.close_empty_tasks()

# update the last_run timestamp with the time that we started the sync.
self.config['tenable']['last_run'] = ts
self.finished_time = datetime.now()
self.config['tenable']['last_run'] = int(self.start_time.timestamp())
self.finished_time = arrow.utcnow()

self.engine.dispose()
# Delete the mapping database.
Expand Down
19 changes: 9 additions & 10 deletions tenb2jira/tenable/tenable.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@


class Tenable:
tvm: (TenableIO | None) = None
tsc: (TenableSC | None) = None
tvm: TenableIO
tsc: TenableSC
config: dict
platform: str
timestamp: int
Expand Down Expand Up @@ -82,11 +82,10 @@ def get_tvm_generator(self) -> Generator[Any, Any, Any]:
close_accepted=self.close_accepted,
)

def get_tsc_generator(self) -> Generator[Any, Any, Any]:
def get_tsc_generator(self, start_time: int) -> Generator[Any, Any, Any]:
"""
Queries the Analysis API and returns the TSC Generator.
"""
self.last_run = int(arrow.now().timestamp())

# The severity map to link the string severities to the integer values
# that TSC expects.
Expand All @@ -99,7 +98,7 @@ def get_tsc_generator(self) -> Generator[Any, Any, Any]:
}

# Construct the TSC timestamp offsets.
tsc_ts = f'{self.timestamp}-{self.last_run}'
tsc_ts = f'{self.timestamp}-{start_time}'

# The base parameters to pass to the API.
params = {
Expand Down Expand Up @@ -136,14 +135,15 @@ def get_tsc_generator(self) -> Generator[Any, Any, Any]:
close_accepted=self.close_accepted,
)

def get_generator(self) -> (Generator[Any, Any, Any] | None):
def get_generator(self,
start_time: arrow.Arrow
) -> Generator[Any, Any, Any]:
"""
Retreives the appropriate generator based on the configured platform.
"""
if self.platform == 'tvm':
return self.get_tvm_generator()
if self.platform == 'tsc':
return self.get_tsc_generator()
return self.get_tsc_generator(int(start_time.timestamp()))

def get_asset_cleanup(self) -> (Generator[Any, Any, Any] | list):
if self.platform == 'tvm':
Expand All @@ -154,5 +154,4 @@ def get_asset_cleanup(self) -> (Generator[Any, Any, Any] | list):
chunk_size=self.chunk_size
)
return tvm_asset_cleanup(dassets, tassets)
else:
return []
return []
2 changes: 1 addition & 1 deletion tenb2jira/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = '2.0.9'
version = '2.0.10'
43 changes: 40 additions & 3 deletions tests/jira/test_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,43 @@ def field_config():
}


def test_field_repr(field_config):
f = Field(config=field_config,
platform='tvm',
platform_map={'tvm': 'Test Platform'}
)
assert repr(f) == 'Field(1: Test Field)'


def test_field_attr(field_config):
f = Field(config=field_config,
platform='tvm',
platform_map={'tvm': 'Test Platform'}
)
assert f.attr == 'test'

field_config.pop('attr', None)
field_config['platform_id'] = True
f = Field(config=field_config,
platform='tvm',
platform_map={'tvm': 'Test Platform'}
)
assert f.attribute == None
assert f.platform_id == 'Test Platform'
assert f.attr == 'Test Platform'

field_config.pop('platform_id', None)
field_config['static_value'] = 'static'
f = Field(config=field_config,
platform='tvm',
platform_map={'tvm': 'Test Platform'}
)
assert f.attribute == None
assert f.platform_id == None
assert f.static_value == 'static'
assert f.attr == 'static'


def test_field_noapi(field_config):
f = Field(config=field_config,
platform='tvm',
Expand Down Expand Up @@ -130,9 +167,9 @@ def test_field_parse_value_float(field_config):
platform_map={'tvm': 'Test Platform'}
)
f.type = 'float'
assert f.parse_value({'test': 1}) == '1.0'
assert f.parse_value({'test': 1.0}) == '1.0'
assert f.parse_value({'test': '1'}) == '1.0'
assert f.parse_value({'test': 1}) == 1.0
assert f.parse_value({'test': 1.0}) == 1.0
assert f.parse_value({'test': '1'}) == 1.0


def test_field_parse_value_datetime(field_config):
Expand Down
Loading
Loading