From b3d4bc72dcdb89719783b2ade583a469a3e09a72 Mon Sep 17 00:00:00 2001 From: Benjamin Drung Date: Wed, 5 Mar 2025 15:46:34 +0100 Subject: [PATCH] Replace deprecated datetime.utcnow() by datetime.now(timezone.utc) Fixes: https://github.com/IdentityPython/pysaml2/issues/934 Since Python 3.12, the utcnow function is deprecated. see, https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow This commit turns usages of `utcnow()` into `now(timezone.UTC)`. The solution is based on the suggestion from official Python docs: https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow Replace `datetime.datetime.utcnow()` with `datetime.datetime.now(datetime.timezone.utc)`. At the same time, the pytz dependency is removed in favour of the standard Python functions. Signed-off-by: Ivan Kanakarakis --- poetry.lock | 26 +------------------------- pyproject.toml | 2 -- src/saml2/cert.py | 6 +++--- src/saml2/mongo_store.py | 7 ++++--- src/saml2/sigver.py | 8 ++++---- src/saml2/time_util.py | 9 +++++---- tests/test_41_response.py | 5 +++-- tests/test_44_authnresp.py | 3 ++- 8 files changed, 22 insertions(+), 44 deletions(-) diff --git a/poetry.lock b/poetry.lock index bf1254214..47fe20c73 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1407,18 +1407,6 @@ files = [ [package.dependencies] six = ">=1.5" -[[package]] -name = "pytz" -version = "2025.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, - {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -1860,18 +1848,6 @@ files = [ {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, ] -[[package]] -name = "types-pytz" -version = "2022.7.1.2" -description = "Typing stubs for pytz" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "types-pytz-2022.7.1.2.tar.gz", hash = "sha256:487d3e8e9f4071eec8081746d53fa982bbc05812e719dcbf2ebf3d55a1a4cd28"}, - {file = "types_pytz-2022.7.1.2-py3-none-any.whl", hash = "sha256:40ca448a928d566f7d44ddfde0066e384f7ffbd4da2778e42a4570eaca572446"}, -] - [[package]] name = "types-requests" version = "2.32.0.20241016" @@ -2097,4 +2073,4 @@ s2repoze = ["paste", "repoze.who", "zope.interface"] [metadata] lock-version = "2.1" python-versions = "^3.9" -content-hash = "51de515b9091282bd147073bbeb38b175a32fe3850f926ba860ee411f3a2b70f" +content-hash = "08eae4c86f73876517dfd1574f6766406c3a70f1d5bc656eabcf7e34e5b6b52e" diff --git a/pyproject.toml b/pyproject.toml index ff43a3f22..8ca901d0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,6 @@ dependencies = [ "defusedxml", "pyopenssl <24.3.0", "python-dateutil", - "pytz", "requests >=2.0.0,<3.0.0", # ^2 means compatible with 2.x "xmlschema >=2.0.0,<3.0.0" ] @@ -69,7 +68,6 @@ ipdb = "^0.13.9" mypy = "^1.0.0" types-pyopenssl = "^23.0.0.3" types-python-dateutil = "^2.8.19.6" -types-pytz = "^2022.7.1.0" types-setuptools = "^67.2.0.1" types-six = "^1.16.21.4" types-requests = "^2.28.11.12" diff --git a/src/saml2/cert.py b/src/saml2/cert.py index c5f626601..e90651e44 100644 --- a/src/saml2/cert.py +++ b/src/saml2/cert.py @@ -1,13 +1,13 @@ __author__ = "haho0032" import base64 -import datetime from os import remove from os.path import join +from datetime import datetime +from datetime import timezone from OpenSSL import crypto import dateutil.parser -import pytz import saml2.cryptography.pki @@ -278,7 +278,7 @@ def verify_chain(self, cert_chain_str_list, cert_str): def certificate_not_valid_yet(self, cert): starts_to_be_valid = dateutil.parser.parse(cert.get_notBefore()) - now = pytz.UTC.localize(datetime.datetime.utcnow()) + now = datetime.now(timezone.utc) if starts_to_be_valid < now: return False return True diff --git a/src/saml2/mongo_store.py b/src/saml2/mongo_store.py index 14be9c9ff..44175c82f 100644 --- a/src/saml2/mongo_store.py +++ b/src/saml2/mongo_store.py @@ -1,6 +1,7 @@ -import datetime -from hashlib import sha1 import logging +from hashlib import sha1 +from datetime import datetime +from datetime import timezone from pymongo import MongoClient import pymongo.errors @@ -205,7 +206,7 @@ def store(self, value, **kwargs): doc.update(kwargs) # Add timestamp to all documents to allow external garbage collecting if "created_at" not in doc: - doc["created_at"] = datetime.datetime.utcnow() + doc["created_at"] = datetime.now(timezone.utc) _ = self.db.insert_one(doc) def get(self, value=None, **kwargs): diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py index ddb5f9221..738ac04b1 100644 --- a/src/saml2/sigver.py +++ b/src/saml2/sigver.py @@ -3,13 +3,14 @@ """ import base64 -import datetime -from importlib.resources import files as _resource_files import hashlib import itertools import logging import os import re +from datetime import datetime +from datetime import timezone +from importlib.resources import files as _resource_files from subprocess import PIPE from subprocess import Popen from tempfile import NamedTemporaryFile @@ -19,7 +20,6 @@ from OpenSSL import crypto import dateutil -import pytz from saml2 import ExtensionElement from saml2 import SamlBase @@ -377,7 +377,7 @@ def active_cert(key): except AttributeError: return False - now = pytz.UTC.localize(datetime.datetime.utcnow()) + now = datetime.now(timezone.utc) valid_from = dateutil.parser.parse(cert.get_notBefore()) valid_to = dateutil.parser.parse(cert.get_notAfter()) active = not cert.has_expired() and valid_from <= now < valid_to diff --git a/src/saml2/time_util.py b/src/saml2/time_util.py index c3e5b1393..86ad711d8 100644 --- a/src/saml2/time_util.py +++ b/src/saml2/time_util.py @@ -6,11 +6,12 @@ """ import calendar -from datetime import datetime -from datetime import timedelta import re import sys import time +from datetime import datetime +from datetime import timezone +from datetime import timedelta TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" @@ -175,7 +176,7 @@ def time_in_a_while(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0 :return: UTC time """ delta = timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks) - return datetime.utcnow() + delta + return datetime.now(timezone.utc) + delta def time_a_while_ago(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0): @@ -185,7 +186,7 @@ def time_a_while_ago(days=0, seconds=0, microseconds=0, milliseconds=0, minutes= minutes[, hours[, weeks]]]]]]]) """ delta = timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks) - return datetime.utcnow() - delta + return datetime.now(timezone.utc) - delta def in_a_while(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0, format=TIME_FORMAT): diff --git a/tests/test_41_response.py b/tests/test_41_response.py index 59f688885..e6f26400a 100644 --- a/tests/test_41_response.py +++ b/tests/test_41_response.py @@ -1,7 +1,8 @@ #!/usr/bin/env python import logging from contextlib import closing -import datetime +from datetime import datetime +from datetime import timezone from unittest.mock import Mock from unittest.mock import patch @@ -127,7 +128,7 @@ def test_issuer_none(self): @patch("saml2.time_util.datetime") def test_false_sign(self, mock_datetime, caplog): caplog.set_level(logging.ERROR) - mock_datetime.utcnow = Mock(return_value=datetime.datetime(2016, 9, 4, 9, 59, 39)) + mock_datetime.now = Mock(return_value=datetime(2016, 9, 4, 9, 59, 39, tzinfo=timezone.utc)) with open(FALSE_ASSERT_SIGNED) as fp: xml_response = fp.read() diff --git a/tests/test_44_authnresp.py b/tests/test_44_authnresp.py index 5be61d9b5..17db75a4a 100644 --- a/tests/test_44_authnresp.py +++ b/tests/test_44_authnresp.py @@ -1,6 +1,7 @@ #!/usr/bin/env python from contextlib import closing from datetime import datetime +from datetime import timezone from dateutil import parser from pathutils import dotname @@ -131,7 +132,7 @@ def test_verify_w_authn(self): assert len(authn_info) == 1 assert authn_info[0][0] == INTERNETPROTOCOLPASSWORD assert authn_info[0][1] == ["http://www.example.com/login"] - now = datetime.utcnow() + now = datetime.now(timezone.utc) dt = parser.parse(authn_info[0][2]) assert now.year == dt.year and now.month == dt.month and now.day == dt.day session_info = self.ar.session_info()