mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
handle Y2038 in timestamp to datetime conversions
According to datetime.utcfromtimestamp() method documentation[1], this and similar methods fail for dates past 2038 and can be replaced by the following expression on the POSIX compliant systems: datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(seconds=timestamp) Make sure to use a method that at least allows to import the timestamps properly to datetime objects on 32-bit platforms. [1] https://docs.python.org/3/library/datetime.html#datetime.datetime.utcfromtimestamp Fixes: https://pagure.io/freeipa/issue/8378 Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com> Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
parent
a3c648bd92
commit
1f6ca418ee
@ -22,6 +22,7 @@ import datetime
|
||||
import email.utils
|
||||
from calendar import timegm
|
||||
from urllib.parse import urlparse
|
||||
from ipapython.ipautil import datetime_from_utctimestamp
|
||||
|
||||
|
||||
'''
|
||||
@ -184,7 +185,7 @@ class Cookie:
|
||||
# use the RFC 1123 parsing function which uses only English
|
||||
|
||||
try:
|
||||
dt = datetime.datetime(*email.utils.parsedate(s)[0:6])
|
||||
dt = email.utils.parsedate_to_datetime(s)
|
||||
except Exception as e:
|
||||
raise ValueError("unable to parse expires datetime '%s': %s" % (s, e))
|
||||
|
||||
@ -390,7 +391,7 @@ class Cookie:
|
||||
elif isinstance(value, datetime.datetime):
|
||||
self._timestamp = value
|
||||
elif isinstance(value, (int, float)):
|
||||
self._timestamp = datetime.datetime.utcfromtimestamp(value)
|
||||
self._timestamp = datetime_from_utctimestamp(value, units=1)
|
||||
elif isinstance(value, str):
|
||||
self._timestamp = Cookie.parse_datetime(value)
|
||||
else:
|
||||
@ -415,8 +416,10 @@ class Cookie:
|
||||
self._expires = None
|
||||
elif isinstance(value, datetime.datetime):
|
||||
self._expires = value
|
||||
if self._expires.tzinfo is None:
|
||||
self._expires.replace(tzinfo=datetime.timezone.utc)
|
||||
elif isinstance(value, (int, float)):
|
||||
self._expires = datetime.datetime.utcfromtimestamp(value)
|
||||
self._expires = datetime_from_utctimestamp(value, units=1)
|
||||
elif isinstance(value, str):
|
||||
self._expires = Cookie.parse_datetime(value)
|
||||
else:
|
||||
|
@ -1647,3 +1647,27 @@ def rmtree(path):
|
||||
shutil.rmtree(path)
|
||||
except Exception as e:
|
||||
logger.error('Error removing %s: %s', path, str(e))
|
||||
|
||||
|
||||
def datetime_from_utctimestamp(t, units=1):
|
||||
"""
|
||||
Convert a timestamp or a time.struct_time to a datetime.datetime
|
||||
object down to seconds, with UTC timezone
|
||||
|
||||
The conversion is safe for year 2038 problem
|
||||
|
||||
:param t: int or float timestamp in (milli)seconds since UNIX epoch
|
||||
or time.struct_time
|
||||
:param units: normalizing factor for the timestamp
|
||||
(1 for seconds, 1000 for milliseconds)
|
||||
defaults to 1
|
||||
:return: datetime.datetime object in UTC timezone
|
||||
"""
|
||||
if isinstance(t, time.struct_time):
|
||||
v = int(time.mktime(t))
|
||||
elif isinstance(t, (float, int)):
|
||||
v = int(t)
|
||||
else:
|
||||
raise TypeError(t)
|
||||
epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
|
||||
return epoch + datetime.timedelta(seconds=v // units)
|
||||
|
@ -2,7 +2,6 @@
|
||||
# Copyright (C) 2020 FreeIPA Contributors see COPYING for license
|
||||
#
|
||||
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from ipaserver.dnssec._odsbase import AbstractODSDBConnection
|
||||
@ -39,7 +38,10 @@ class ODSDBConnection(AbstractODSDBConnection):
|
||||
for row in cur:
|
||||
key = dict()
|
||||
key['HSMkey_id'] = row['locator']
|
||||
key['generate'] = str(datetime.fromtimestamp(row['inception']))
|
||||
key['generate'] = ipautil.datetime_from_utctimestamp(
|
||||
row['inception'],
|
||||
units=1).replace(tzinfo=None).isoformat(
|
||||
sep=' ', timespec='seconds')
|
||||
key['algorithm'] = row['algorithm']
|
||||
key['publish'] = key['generate']
|
||||
key['active'] = None
|
||||
|
@ -52,6 +52,7 @@ from ipalib.request import context
|
||||
from ipalib import output
|
||||
from ipapython import dnsutil, kerberos
|
||||
from ipapython.dn import DN
|
||||
from ipapython.ipautil import datetime_from_utctimestamp
|
||||
from ipaserver.plugins.service import normalize_principal, validate_realm
|
||||
from ipaserver.masters import (
|
||||
ENABLED_SERVICE, CONFIGURED_SERVICE, is_service_enabled
|
||||
@ -254,8 +255,9 @@ def normalize_pkidate(value):
|
||||
|
||||
|
||||
def convert_pkidatetime(value):
|
||||
value = datetime.datetime.fromtimestamp(int(value) // 1000)
|
||||
return x509.format_datetime(value)
|
||||
if isinstance(value, str):
|
||||
value = int(value)
|
||||
return x509.format_datetime(datetime_from_utctimestamp(value, units=1000))
|
||||
|
||||
|
||||
def normalize_serial_number(num):
|
||||
|
@ -241,7 +241,6 @@ digits and nothing else follows.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -651,12 +650,14 @@ def parse_check_request_result_xml(doc):
|
||||
|
||||
updated_on = doc.xpath('//xml/header/updatedOn[1]')
|
||||
if len(updated_on) == 1:
|
||||
updated_on = datetime.datetime.utcfromtimestamp(int(updated_on[0].text))
|
||||
updated_on = ipautil.datetime_from_utctimestamp(
|
||||
int(updated_on[0].text), units=1)
|
||||
response['updated_on'] = updated_on
|
||||
|
||||
created_on = doc.xpath('//xml/header/createdOn[1]')
|
||||
if len(created_on) == 1:
|
||||
created_on = datetime.datetime.utcfromtimestamp(int(created_on[0].text))
|
||||
created_on = ipautil.datetime_from_utctimestamp(
|
||||
int(created_on[0].text), units=1)
|
||||
response['created_on'] = created_on
|
||||
|
||||
request_notes = doc.xpath('//xml/header/requestNotes[1]')
|
||||
|
@ -19,9 +19,8 @@
|
||||
|
||||
import datetime
|
||||
import email.utils
|
||||
import calendar
|
||||
from ipapython.cookie import Cookie
|
||||
|
||||
from ipapython.ipautil import datetime_from_utctimestamp
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.tier0
|
||||
@ -148,17 +147,21 @@ class TestExpires:
|
||||
@pytest.fixture(autouse=True)
|
||||
def expires_setup(self):
|
||||
# Force microseconds to zero because cookie timestamps only have second resolution
|
||||
self.now = datetime.datetime.utcnow().replace(microsecond=0)
|
||||
self.now_timestamp = calendar.timegm(self.now.utctimetuple())
|
||||
self.now = datetime.datetime.now(
|
||||
tz=datetime.timezone.utc).replace(microsecond=0)
|
||||
self.now_timestamp = datetime_from_utctimestamp(
|
||||
self.now.utctimetuple(), units=1).timestamp()
|
||||
self.now_string = email.utils.formatdate(self.now_timestamp, usegmt=True)
|
||||
|
||||
self.max_age = 3600 # 1 hour
|
||||
self.age_expiration = self.now + datetime.timedelta(seconds=self.max_age)
|
||||
self.age_timestamp = calendar.timegm(self.age_expiration.utctimetuple())
|
||||
self.age_timestamp = datetime_from_utctimestamp(
|
||||
self.age_expiration.utctimetuple(), units=1).timestamp()
|
||||
self.age_string = email.utils.formatdate(self.age_timestamp, usegmt=True)
|
||||
|
||||
self.expires = self.now + datetime.timedelta(days=1) # 1 day
|
||||
self.expires_timestamp = calendar.timegm(self.expires.utctimetuple())
|
||||
self.expires_timestamp = datetime_from_utctimestamp(
|
||||
self.expires.utctimetuple(), units=1).timestamp()
|
||||
self.expires_string = email.utils.formatdate(self.expires_timestamp, usegmt=True)
|
||||
|
||||
def test_expires(self):
|
||||
@ -327,7 +330,8 @@ class TestAttributes:
|
||||
assert cookie.max_age is None
|
||||
|
||||
cookie.expires = 'Sun, 06 Nov 1994 08:49:37 GMT'
|
||||
assert cookie.expires == datetime.datetime(1994, 11, 6, 8, 49, 37)
|
||||
assert cookie.expires == datetime.datetime(
|
||||
1994, 11, 6, 8, 49, 37, tzinfo=datetime.timezone.utc)
|
||||
cookie.expires = None
|
||||
assert cookie.expires is None
|
||||
|
||||
@ -433,17 +437,21 @@ class TestNormalization:
|
||||
@pytest.fixture(autouse=True)
|
||||
def normalization_setup(self):
|
||||
# Force microseconds to zero because cookie timestamps only have second resolution
|
||||
self.now = datetime.datetime.utcnow().replace(microsecond=0)
|
||||
self.now_timestamp = calendar.timegm(self.now.utctimetuple())
|
||||
self.now = datetime.datetime.now(
|
||||
tz=datetime.timezone.utc).replace(microsecond=0)
|
||||
self.now_timestamp = datetime_from_utctimestamp(
|
||||
self.now.utctimetuple(), units=1).timestamp()
|
||||
self.now_string = email.utils.formatdate(self.now_timestamp, usegmt=True)
|
||||
|
||||
self.max_age = 3600 # 1 hour
|
||||
self.age_expiration = self.now + datetime.timedelta(seconds=self.max_age)
|
||||
self.age_timestamp = calendar.timegm(self.age_expiration.utctimetuple())
|
||||
self.age_timestamp = datetime_from_utctimestamp(
|
||||
self.age_expiration.utctimetuple(), units=1).timestamp()
|
||||
self.age_string = email.utils.formatdate(self.age_timestamp, usegmt=True)
|
||||
|
||||
self.expires = self.now + datetime.timedelta(days=1) # 1 day
|
||||
self.expires_timestamp = calendar.timegm(self.expires.utctimetuple())
|
||||
self.expires_timestamp = datetime_from_utctimestamp(
|
||||
self.expires.utctimetuple(), units=1).timestamp()
|
||||
self.expires_string = email.utils.formatdate(self.expires_timestamp, usegmt=True)
|
||||
|
||||
def test_path_normalization(self):
|
||||
|
Loading…
Reference in New Issue
Block a user