Remove iso8601 dependency

Our 'expires' strings are constrained by the ISO8601_DATETIME_SCHEMA
which matches regex '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z'. This can be
parsed with just a datetime.strptime(): iso8601 module is not needed.

* Add formats.expiry_string_to_datetime() helper function
* Modify the 3 locations that used iso8601 and the api/metadata.py usage
  of datetime.strptime()
* Remove related unnecessary logger setup
* Add the missing exception documentation to relevant functions (in many
  cases the exception is rather unlikely as the schema has been verified
  many times before this though...)

Fixes #1065

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
This commit is contained in:
Jussi Kukkonen 2020-10-13 16:06:39 +03:00
parent f8606d9645
commit 2f69986e2b
9 changed files with 62 additions and 41 deletions

View file

@ -6,7 +6,6 @@ cryptography==3.1.1 # via securesystemslib
enum34==1.1.6 ; python_version < '3' # via cryptography
idna==2.10 # via requests
ipaddress==1.0.23 ; python_version < '3' # via cryptography
iso8601==0.1.13
pycparser==2.20 # via cffi
pynacl==1.4.0 # via securesystemslib
python-dateutil==2.8.1 # via securesystemslib

View file

@ -44,4 +44,3 @@
securesystemslib[colors, crypto, pynacl]
requests
six
iso8601

View file

@ -114,7 +114,6 @@
},
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4",
install_requires = [
'iso8601>=0.1.12',
'requests>=2.19.1',
'six>=1.11.0',
'securesystemslib>=0.16.0'

View file

@ -723,6 +723,23 @@ def test_build_dict_conforming_to_schema(self):
def test_expiry_string_to_datetime(self):
dt = tuf.formats.expiry_string_to_datetime('1985-10-21T13:20:00Z')
self.assertEqual(dt, datetime.datetime(1985, 10, 21, 13, 20, 0))
dt = tuf.formats.expiry_string_to_datetime('2038-01-19T03:14:08Z')
self.assertEqual(dt, datetime.datetime(2038, 1, 19, 3, 14, 8))
# First 3 fail via securesystemslib schema, last one because of strptime()
invalid_inputs = [
'2038-1-19T03:14:08Z', # leading zeros not optional
'2038-01-19T031408Z', # strict time parsing
'2038-01-19T03:14:08Z-06:00', # timezone not allowed
'2038-13-19T03:14:08Z', # too many months
]
for invalid_input in invalid_inputs:
with self.assertRaises(securesystemslib.exceptions.FormatError):
tuf.formats.expiry_string_to_datetime(invalid_input)
def test_unix_timestamp_to_datetime(self):

View file

@ -294,9 +294,8 @@ def from_dict(cls, signed_dict: JsonDict) -> 'Signed':
# Convert 'expires' TUF metadata string to a datetime object, which is
# what the constructor expects and what we store. The inverse operation
# is implemented in 'to_dict'.
signed_dict['expires'] = datetime.strptime(
signed_dict['expires'],
"%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=None)
signed_dict['expires'] = tuf.formats.expiry_string_to_datetime(
signed_dict['expires'])
# NOTE: We write the converted 'expires' back into 'signed_dict' above
# so that we can pass it to the constructor as '**signed_dict' below,
# along with other fields that belong to Signed subclasses.

View file

@ -145,7 +145,6 @@
import securesystemslib.keys
import securesystemslib.util
import six
import iso8601
import requests.exceptions
# The Timestamp role does not have signed metadata about it; otherwise we
@ -163,11 +162,6 @@
# See 'log.py' to learn how logging is handled in TUF.
logger = logging.getLogger(__name__)
# Disable 'iso8601' logger messages to prevent 'iso8601' from clogging the
# log file.
iso8601_logger = logging.getLogger('iso8601')
iso8601_logger.disabled = True
class MultiRepoUpdater(object):
"""
@ -2377,7 +2371,8 @@ def _ensure_not_expired(self, metadata_object, metadata_rolename):
<Exceptions>
tuf.exceptions.ExpiredMetadataError:
If 'metadata_rolename' has expired.
securesystemslib.exceptions.FormatError:
If the expiration cannot be parsed correctly
<Side Effects>
None.
@ -2385,22 +2380,15 @@ def _ensure_not_expired(self, metadata_object, metadata_rolename):
None.
"""
# Extract the expiration time.
expires = metadata_object['expires']
# If the current time has surpassed the expiration date, raise an
# exception. 'expires' is in
# 'securesystemslib.formats.ISO8601_DATETIME_SCHEMA' format (e.g.,
# '1985-10-21T01:22:00Z'.) Convert it to a unix timestamp and compare it
# Extract the expiration time. Convert it to a unix timestamp and compare it
# against the current time.time() (also in Unix/POSIX time format, although
# with microseconds attached.)
current_time = int(time.time())
# Generate a user-friendly error message if 'expires' is less than the
# current time (i.e., a local time.)
expires_datetime = iso8601.parse_date(expires)
expires_datetime = tuf.formats.expiry_string_to_datetime(
metadata_object['expires'])
expires_timestamp = tuf.formats.datetime_to_unix_timestamp(expires_datetime)
current_time = int(time.time())
if expires_timestamp < current_time:
message = 'Metadata '+repr(metadata_rolename)+' expired on ' + \
expires_datetime.ctime() + ' (UTC).'

View file

@ -612,6 +612,37 @@ def build_dict_conforming_to_schema(schema, **kwargs):
def expiry_string_to_datetime(expires):
"""
<Purpose>
Convert an expiry string to a datetime object.
<Arguments>
expires:
The expiry date-time string in the ISO8601 format that is defined
in securesystemslib.ISO8601_DATETIME_SCHEMA. E.g. '2038-01-19T03:14:08Z'
<Exceptions>
securesystemslib.exceptions.FormatError, if 'expires' cannot be
parsed correctly.
<Side Effects>
None.
<Returns>
A datetime object representing the expiry time.
"""
# Raise 'securesystemslib.exceptions.FormatError' if there is a mismatch.
securesystemslib.formats.ISO8601_DATETIME_SCHEMA.check_match(expires)
try:
return datetime.datetime.strptime(expires, "%Y-%m-%dT%H:%M:%SZ")
except ValueError as error:
six.raise_from(securesystemslib.exceptions.FormatError(
'Failed to parse ' + repr(expires) + ' as an expiry time'),
error)
def datetime_to_unix_timestamp(datetime_object):
"""
<Purpose>

View file

@ -52,7 +52,6 @@
import securesystemslib.hash
import securesystemslib.interface
import securesystemslib.util
import iso8601
import six
import securesystemslib.storage
@ -61,11 +60,6 @@
# See 'log.py' to learn how logging is handled in TUF.
logger = logging.getLogger(__name__)
# Disable 'iso8601' logger messages to prevent 'iso8601' from clogging the
# log file.
iso8601_logger = logging.getLogger('iso8601')
iso8601_logger.disabled = True
# The extension of TUF metadata.
METADATA_EXTENSION = '.json'
@ -704,7 +698,8 @@ def _log_warning_if_expires_soon(rolename, expires_iso8601_timestamp,
# unix timestamp, subtract from current time.time() (also in POSIX time)
# and compare against 'seconds_remaining_to_warn'. Log a warning message
# to console if 'rolename' expires soon.
datetime_object = iso8601.parse_date(expires_iso8601_timestamp)
datetime_object = tuf.formats.expiry_string_to_datetime(
expires_iso8601_timestamp)
expires_unix_timestamp = \
tuf.formats.datetime_to_unix_timestamp(datetime_object)
seconds_until_expires = expires_unix_timestamp - int(time.time())

View file

@ -53,7 +53,6 @@
import securesystemslib.keys
import securesystemslib.formats
import securesystemslib.util
import iso8601
import six
import securesystemslib.storage
@ -1317,15 +1316,12 @@ def expiration(self):
<Purpose>
A getter method that returns the role's expiration datetime.
>>>
>>>
>>>
<Arguments>
None.
<Exceptions>
None.
securesystemslib.exceptions.FormatError, if the expiration cannot be
parsed correctly
<Side Effects>
None.
@ -1337,9 +1333,7 @@ def expiration(self):
roleinfo = tuf.roledb.get_roleinfo(self.rolename, self._repository_name)
expires = roleinfo['expires']
expires_datetime_object = iso8601.parse_date(expires)
return expires_datetime_object
return tuf.formats.expiry_string_to_datetime(expires)