Skip to content

Commit

Permalink
Merge pull request #66 from RedHatProductSecurity/fix-datetime-parsing
Browse files Browse the repository at this point in the history
Fix datetime parsing
  • Loading branch information
mprpic authored May 22, 2023
2 parents ff0bb46 + c078894 commit 192de96
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 33 deletions.
41 changes: 36 additions & 5 deletions cvelib/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
import sys
from collections import defaultdict
from datetime import date, datetime
from datetime import date, datetime, timezone
from functools import wraps
from typing import Any, Callable, DefaultDict, List, Optional, Sequence, TextIO, Union

Expand Down Expand Up @@ -39,7 +39,32 @@ def validate_year(


def human_ts(ts: str) -> str:
return datetime.strptime(ts, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%c")
"""Return a consistent, human-readable datetime.
If the format cannot be parsed against RFC3339 or ISO8601, return the original raw value.
Note: this helper function should only be used for printing datetime values.
"""
dt = None
for dt_format in (
"%Y-%m-%dT%H:%M:%S", # 2019-03-27T19:20:26
"%Y-%m-%dT%H:%M:%S.%f%z", # 2019-03-27T19:20:26.123+01:00
"%Y-%m-%dT%H:%M:%S%z", # 2019-03-27T19:20:26+01:00
"%Y-%m-%dT%H:%M:%S.%fZ", # 2019-03-27T19:20:26.123Z
"%Y-%m-%dT%H:%M:%S%Z", # 2019-03-27T19:20:26Z
):
try:
dt = datetime.strptime(ts, dt_format)
except ValueError:
pass
else:
break
if dt:
if not dt.tzinfo:
dt = dt.replace(tzinfo=timezone.utc)
return dt.strftime("%c %z")
else:
return ts


def print_cve_id(cve: dict) -> None:
Expand All @@ -49,16 +74,22 @@ def print_cve_id(cve: dict) -> None:
if "requested_by" in cve:
click.echo(f"├─ Owning CNA:\t{cve['owning_cna']}")
click.echo(f"├─ Reserved by:\t{cve['requested_by']['user']} ({cve['requested_by']['cna']})")
click.echo(f"└─ Reserved on:\t{human_ts(cve['reserved'])}")
if "time" in cve:
click.echo(f"├─ Reserved on:\t{human_ts(cve['reserved'])}")
click.echo(f"└─ Updated on:\t{human_ts(cve['time']['modified'])}")
else:
click.echo(f"└─ Reserved on:\t{human_ts(cve['reserved'])}")
else:
click.echo(f"└─ Owning CNA:\t{cve['owning_cna']}")
click.echo(f"├─ Owning CNA:\t{cve['owning_cna']}")
click.echo(f"└─ Updated on:\t{human_ts(cve['dateUpdated'])}")


def print_cve_record(cve: dict) -> None:
click.secho(cve["cveMetadata"]["cveId"], bold=True)
click.echo(f"├─ State:\t{cve['cveMetadata']['state']}")
click.echo(f"├─ Owning CNA:\t{cve['cveMetadata']['assignerShortName']}")
click.echo(f"└─ Reserved on:\t{human_ts(cve['cveMetadata']['dateReserved'])}")
click.echo(f"├─ Reserved on:\t{human_ts(cve['cveMetadata'].get('dateReserved', 'N/A'))}")
click.echo(f"└─ Updated on:\t{human_ts(cve['cveMetadata'].get('dateUpdated', 'N/A'))}")


def print_table(lines: Sequence[Sequence[str]], highlight_header: bool = True) -> None:
Expand Down
69 changes: 41 additions & 28 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def test_cve_id_show(show_cve_record, show_cve_id):
"├─ State:\tRESERVED\n"
"├─ Owning CNA:\tacme\n"
"├─ Reserved by:\t[email protected] (acme)\n"
"└─ Reserved on:\tThu Jan 14 18:35:17 2021\n"
"├─ Reserved on:\tThu Jan 14 18:35:17 2021 +0000\n"
"└─ Updated on:\tThu Jan 14 18:35:17 2021 +0000\n"
)
assert not show_cve_record.called

Expand Down Expand Up @@ -79,7 +80,8 @@ def test_cve_show_full(show_cve_record, show_cve_id):
"├─ State:\tRESERVED\n"
"├─ Owning CNA:\tacme\n"
"├─ Reserved by:\t[email protected] (acme)\n"
"└─ Reserved on:\tThu Jan 14 18:35:17 2021\n"
"├─ Reserved on:\tThu Jan 14 18:35:17 2021 +0000\n"
"└─ Updated on:\tThu Jan 14 18:35:17 2021 +0000\n"
)
# Don't bother checking the data since we provide it as a fixture anyway. Simply check that a
# JSON is displayed and the length of the printed string is something reasonable.
Expand Down Expand Up @@ -133,16 +135,19 @@ def test_cve_list():
assert result.exit_code == 0, result.output
assert result.output == (
"CVE ID STATE OWNING CNA RESERVED BY RESERVED ON\n"
"CVE-2021-3001 RESERVED acme bob (acme) Thu Jan 14 18:32:19 2021\n"
"CVE-2021-3002 PUBLISHED acme ann (acme) Thu Jan 14 18:32:57 2021\n"
"CVE-2021-3003 REJECTED acme eve (corp) Thu Jan 14 18:34:50 2021\n"
"CVE-2021-3001 RESERVED acme bob (acme) "
"Thu Jan 14 18:32:19 2021 +0000\n"
"CVE-2021-3002 PUBLISHED acme ann (acme) "
"Thu Jan 14 18:32:57 2021 +0000\n"
"CVE-2021-3003 REJECTED acme eve (corp) "
"Thu Jan 14 18:34:50 2021 +0000\n"
)
result = runner.invoke(cli, DEFAULT_OPTS + ["list", "--no-header"])
assert result.exit_code == 0, result.output
assert result.output == (
"CVE-2021-3001 RESERVED acme bob (acme) Thu Jan 14 18:32:19 2021\n"
"CVE-2021-3002 PUBLISHED acme ann (acme) Thu Jan 14 18:32:57 2021\n"
"CVE-2021-3003 REJECTED acme eve (corp) Thu Jan 14 18:34:50 2021\n"
"CVE-2021-3001 RESERVED acme bob (acme) Thu Jan 14 18:32:19 2021 +0000\n"
"CVE-2021-3002 PUBLISHED acme ann (acme) Thu Jan 14 18:32:57 2021 +0000\n"
"CVE-2021-3003 REJECTED acme eve (corp) Thu Jan 14 18:34:50 2021 +0000\n"
)


Expand Down Expand Up @@ -184,7 +189,8 @@ class TestCvePublish:
"assignerOrgId": "19f229d4-f3d5-4605-bf93-521fa4499c06",
"assignerShortName": "test_org",
"cveId": cve_id,
"datePublished": "2001-05-02T00:00:00Z",
"datePublished": "2022-05-02T00:00:00Z",
"dateUpdated": "2021-06-29T12:33:52.892Z",
"dateReserved": "2021-06-29T12:33:52.892Z",
"requesterUserId": "cb55b254-cf8f-46f6-bfbf-fc1d71ba439a",
"state": "PUBLISHED",
Expand Down Expand Up @@ -212,7 +218,8 @@ def test_cve_publish(self, update_published, publish):
f"{self.cve_id}\n"
"├─ State:\tPUBLISHED\n"
"├─ Owning CNA:\ttest_org\n"
"└─ Reserved on:\tTue Jun 29 12:33:52 2021\n"
"├─ Reserved on:\tTue Jun 29 12:33:52 2021 +0000\n"
"└─ Updated on:\tTue Jun 29 12:33:52 2021 +0000\n"
)
assert not update_published.called

Expand All @@ -239,7 +246,8 @@ def test_cve_publish_from_file(self, update_published, publish, tmp_path):
f"{self.cve_id}\n"
"├─ State:\tPUBLISHED\n"
"├─ Owning CNA:\ttest_org\n"
"└─ Reserved on:\tTue Jun 29 12:33:52 2021\n"
"├─ Reserved on:\tTue Jun 29 12:33:52 2021 +0000\n"
"└─ Updated on:\tTue Jun 29 12:33:52 2021 +0000\n"
)
assert not update_published.called

Expand Down Expand Up @@ -268,7 +276,8 @@ def test_cve_reject_with_record(move_to_rejected, update_rejected, reject):
"assignerOrgId": "19f229d4-f3d5-4605-bf93-521fa4499c06",
"assignerShortName": "test_org",
"cveId": cve_id,
"dateRejected": "2001-05-02T00:00:00Z",
"dateRejected": "2022-05-02T00:00:00Z",
"dateUpdated": "2022-05-02T00:00:00Z",
"dateReserved": "2021-06-29T12:33:52.892Z",
"requesterUserId": "cb55b254-cf8f-46f6-bfbf-fc1d71ba439a",
"state": "REJECTED",
Expand All @@ -289,7 +298,8 @@ def test_cve_reject_with_record(move_to_rejected, update_rejected, reject):
f"{cve_id}\n"
"├─ State:\tREJECTED\n"
"├─ Owning CNA:\ttest_org\n"
"└─ Reserved on:\tTue Jun 29 12:33:52 2021\n"
"├─ Reserved on:\tTue Jun 29 12:33:52 2021 +0000\n"
"└─ Updated on:\tMon May 2 00:00:00 2022 +0000\n"
)
assert not update_rejected.called
assert not move_to_rejected.called
Expand Down Expand Up @@ -324,7 +334,8 @@ def test_cve_reject_without_record(move_to_rejected, update_rejected, reject):
"├─ State:\tREJECTED\n"
"├─ Owning CNA:\tacme\n"
"├─ Reserved by:\t[email protected] (acme)\n"
"└─ Reserved on:\tTue Jun 29 12:33:52 2021\n"
"├─ Reserved on:\tTue Jun 29 12:33:52 2021 +0000\n"
"└─ Updated on:\tTue Jun 29 12:33:52 2021 +0000\n"
)
assert not update_rejected.called
assert not reject.called
Expand Down Expand Up @@ -357,7 +368,8 @@ def test_cve_undo_reject(move_to_reserved):
"├─ State:\tRESERVED\n"
"├─ Owning CNA:\tacme\n"
"├─ Reserved by:\t[email protected] (acme)\n"
"└─ Reserved on:\tTue Jun 29 12:33:52 2021\n"
"├─ Reserved on:\tTue Jun 29 12:33:52 2021 +0000\n"
"└─ Updated on:\tTue Jun 29 12:33:52 2021 +0000\n"
)


Expand Down Expand Up @@ -412,12 +424,12 @@ def test_reserve():
"├─ State:\tRESERVED\n"
"├─ Owning CNA:\ttest_org\n"
"├─ Reserved by:\ttest_user@test_org.com (test_org)\n"
"└─ Reserved on:\tMon May 24 18:14:34 2021\n"
"└─ Reserved on:\tMon May 24 18:14:34 2021 +0000\n"
"CVE-2021-20002\n"
"├─ State:\tRESERVED\n"
"├─ Owning CNA:\ttest_org\n"
"├─ Reserved by:\ttest_user@test_org.com (test_org)\n"
"└─ Reserved on:\tMon May 24 18:14:34 2021\n"
"└─ Reserved on:\tMon May 24 18:14:34 2021 +0000\n"
"\n"
"Remaining quota: 10\n"
)
Expand Down Expand Up @@ -445,8 +457,8 @@ def test_active_user_show():
"Test User — test@user\n"
"├─ Active:\tYes\n"
"├─ Roles:\tADMIN\n"
"├─ Created:\tThu Apr 22 02:09:08 2021\n"
"└─ Modified:\tThu Apr 22 02:09:08 2021\n"
"├─ Created:\tThu Apr 22 02:09:08 2021 +0000\n"
"└─ Modified:\tThu Apr 22 02:09:08 2021 +0000\n"
)


Expand All @@ -472,8 +484,8 @@ def test_inactive_user_show():
"test@user\n"
"├─ Active:\tNo\n"
"├─ Roles:\tNone\n"
"├─ Created:\tThu Apr 22 02:09:08 2021\n"
"└─ Modified:\tThu Apr 22 02:09:08 2021\n"
"├─ Created:\tThu Apr 22 02:09:08 2021 +0000\n"
"└─ Modified:\tThu Apr 22 02:09:08 2021 +0000\n"
)


Expand Down Expand Up @@ -518,11 +530,12 @@ def test_user_list():
result = runner.invoke(cli, DEFAULT_OPTS + ["org", "users"])
assert result.exit_code == 0, result.output
assert result.output == (
"USERNAME NAME ROLES ACTIVE CREATED MODIFIED\n"
"foo Foo None No Wed May 26 19:27:24 2021 "
"Wed May 26 19:32:57 2021\n"
"hello@world Hello World ADMIN Yes Wed May 26 19:27:44 2021 "
"Wed May 26 19:28:52 2021\n"
"USERNAME NAME ROLES ACTIVE CREATED "
"MODIFIED\n"
"foo Foo None No Wed May 26 19:27:24 2021 +0000 "
"Wed May 26 19:32:57 2021 +0000\n"
"hello@world Hello World ADMIN Yes Wed May 26 19:27:44 2021 +0000 "
"Wed May 26 19:28:52 2021 +0000\n"
)


Expand All @@ -543,6 +556,6 @@ def test_show_org():
assert result.output == (
"Test Org — test_org\n"
"├─ Roles:\tCNA\n"
"├─ Created:\tWed Apr 21 02:09:07 2021\n"
"└─ Modified:\tWed Apr 21 02:09:07 2021\n"
"├─ Created:\tWed Apr 21 02:09:07 2021 +0000\n"
"└─ Modified:\tWed Apr 21 02:09:07 2021 +0000\n"
)

0 comments on commit 192de96

Please sign in to comment.