Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-Flask-Login for openSUSE:Factory checked in at 2024-09-09 14:44:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-Flask-Login (Old) and /work/SRC/openSUSE:Factory/.python-Flask-Login.new.10096 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-Flask-Login" Mon Sep 9 14:44:23 2024 rev:9 rq:1199443 version:0.6.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-Flask-Login/python-Flask-Login.changes 2024-01-03 12:30:58.036582471 +0100 +++ /work/SRC/openSUSE:Factory/.python-Flask-Login.new.10096/python-Flask-Login.changes 2024-09-09 14:45:16.516773318 +0200 @@ -1,0 +2,8 @@ +Sun Sep 8 12:45:36 UTC 2024 - Dirk Müller <[email protected]> + +- update to 0.6.3: + * Compatibility with Flask 3 and Werkzeug 3. #813 +- drop 0001-fix-avoid-Deprecated-werkzeug.urls.url_decode.patch + (upstream) + +------------------------------------------------------------------- Old: ---- 0001-fix-avoid-Deprecated-werkzeug.urls.url_decode.patch Flask-Login-0.6.2.tar.gz New: ---- Flask-Login-0.6.3.tar.gz BETA DEBUG BEGIN: Old: * Compatibility with Flask 3 and Werkzeug 3. #813 - drop 0001-fix-avoid-Deprecated-werkzeug.urls.url_decode.patch (upstream) BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-Flask-Login.spec ++++++ --- /var/tmp/diff_new_pack.k2Brxq/_old 2024-09-09 14:45:16.956791622 +0200 +++ /var/tmp/diff_new_pack.k2Brxq/_new 2024-09-09 14:45:16.956791622 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-Flask-Login # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,14 +19,12 @@ %{?sle15_python_module_pythons} %bcond_without test Name: python-Flask-Login -Version: 0.6.2 +Version: 0.6.3 Release: 0 Summary: User session management for Flask License: MIT URL: https://github.com/maxcountryman/flask-login Source: https://files.pythonhosted.org/packages/source/F/Flask-Login/Flask-Login-%{version}.tar.gz -# FIX-UPSTREAM-PATCH -Patch0: 0001-fix-avoid-Deprecated-werkzeug.urls.url_decode.patch BuildRequires: %{python_module base >= 3.7} BuildRequires: %{python_module setuptools} BuildRequires: fdupes ++++++ Flask-Login-0.6.2.tar.gz -> Flask-Login-0.6.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/CHANGES.md new/Flask-Login-0.6.3/CHANGES.md --- old/Flask-Login-0.6.2/CHANGES.md 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/CHANGES.md 2023-10-30 15:52:40.000000000 +0100 @@ -1,6 +1,15 @@ Flask-Login Changelog ===================== + +Version 0.6.3 +------------- + +Released 2023-10-30 + +- Compatibility with Flask 3 and Werkzeug 3. #813 + + Version 0.6.2 ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/PKG-INFO new/Flask-Login-0.6.3/PKG-INFO --- old/Flask-Login-0.6.2/PKG-INFO 2022-07-25 17:15:38.553443000 +0200 +++ new/Flask-Login-0.6.3/PKG-INFO 2023-10-30 15:53:07.562335000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Flask-Login -Version: 0.6.2 +Version: 0.6.3 Summary: User authentication and session management for Flask. Home-page: https://github.com/maxcountryman/flask-login Author: Matthew Frazier @@ -23,6 +23,8 @@ Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE +Requires-Dist: Flask>=1.0.4 +Requires-Dist: Werkzeug>=1.0.1 # Flask-Login diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/requirements/ci-release.txt new/Flask-Login-0.6.3/requirements/ci-release.txt --- old/Flask-Login-0.6.2/requirements/ci-release.txt 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/requirements/ci-release.txt 2023-10-30 15:52:40.000000000 +0100 @@ -5,77 +5,61 @@ # # pip-compile-multi # -bleach==4.1.0 - # via readme-renderer -build==0.7.0 +build==1.0.3 # via -r requirements/ci-release.in -certifi==2021.10.8 +certifi==2023.7.22 # via requests -cffi==1.15.0 - # via cryptography -charset-normalizer==2.0.12 +charset-normalizer==3.3.1 # via requests -colorama==0.4.4 - # via twine -cryptography==36.0.2 - # via secretstorage -docutils==0.18.1 +docutils==0.20.1 # via readme-renderer -idna==3.3 +idna==3.4 # via requests -importlib-metadata==4.11.3 +importlib-metadata==6.8.0 # via # keyring # twine -jeepney==0.7.1 - # via - # keyring - # secretstorage -keyring==23.5.0 +jaraco-classes==3.3.0 + # via keyring +keyring==24.2.0 # via twine -packaging==21.3 - # via - # bleach - # build -pep517==0.12.0 +markdown-it-py==3.0.0 + # via rich +mdurl==0.1.2 + # via markdown-it-py +more-itertools==10.1.0 + # via jaraco-classes +nh3==0.2.14 + # via readme-renderer +packaging==23.2 # via build -pkginfo==1.8.2 +pkginfo==1.9.6 # via twine -pycparser==2.21 - # via cffi -pygments==2.11.2 - # via readme-renderer -pyparsing==3.0.7 - # via packaging -readme-renderer==34.0 +pygments==2.16.1 + # via + # readme-renderer + # rich +pyproject-hooks==1.0.0 + # via build +readme-renderer==42.0 # via # -r requirements/ci-release.in # twine -requests==2.27.1 +requests==2.31.0 # via # requests-toolbelt # twine -requests-toolbelt==0.9.1 +requests-toolbelt==1.0.0 # via twine rfc3986==2.0.0 # via twine -secretstorage==3.3.1 - # via keyring -six==1.16.0 - # via bleach -tomli==2.0.1 - # via - # build - # pep517 -tqdm==4.63.1 +rich==13.6.0 # via twine -twine==3.8.0 +twine==4.0.2 # via -r requirements/ci-release.in -urllib3==1.26.9 +urllib3==2.0.7 # via # requests # twine -webencodings==0.5.1 - # via bleach -zipp==3.7.0 +zipp==3.17.0 # via importlib-metadata diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/requirements/ci-tests.txt new/Flask-Login-0.6.3/requirements/ci-tests.txt --- old/Flask-Login-0.6.2/requirements/ci-tests.txt 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/requirements/ci-tests.txt 2023-10-30 15:52:40.000000000 +0100 @@ -5,45 +5,47 @@ # # pip-compile-multi # -certifi==2021.10.8 +cachetools==5.3.2 + # via tox +certifi==2023.7.22 # via requests -charset-normalizer==2.0.12 +chardet==5.2.0 + # via tox +charset-normalizer==3.3.1 # via requests -coverage==6.3.2 +colorama==0.4.6 + # via tox +coverage==6.5.0 # via coveralls coveralls==3.3.1 # via -r requirements/ci-tests.in -distlib==0.3.4 +distlib==0.3.7 # via virtualenv docopt==0.6.2 # via coveralls -filelock==3.6.0 +filelock==3.13.0 # via # tox # virtualenv -idna==3.3 +idna==3.4 # via requests -packaging==21.3 - # via tox -platformdirs==2.5.1 - # via virtualenv -pluggy==1.0.0 - # via tox -py==1.11.0 - # via tox -pyparsing==3.0.7 - # via packaging -requests==2.27.1 - # via coveralls -six==1.16.0 +packaging==23.2 + # via + # pyproject-api + # tox +platformdirs==3.11.0 # via # tox # virtualenv -toml==0.10.2 +pluggy==1.3.0 + # via tox +pyproject-api==1.6.1 # via tox -tox==3.24.5 +requests==2.31.0 + # via coveralls +tox==4.11.3 # via -r requirements/ci-tests.in -urllib3==1.26.9 +urllib3==2.0.7 # via requests -virtualenv==20.14.0 +virtualenv==20.24.6 # via tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/requirements/dev.txt new/Flask-Login-0.6.3/requirements/dev.txt --- old/Flask-Login-0.6.2/requirements/dev.txt 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/requirements/dev.txt 2023-10-30 15:52:40.000000000 +0100 @@ -8,21 +8,31 @@ -r docs.txt -r style.txt -r tests.txt -click==8.1.0 +build==1.0.3 + # via pip-tools +cachetools==5.3.2 + # via tox +chardet==5.2.0 + # via tox +click==8.1.7 # via # pip-compile-multi # pip-tools -pep517==0.12.0 - # via pip-tools -pip-compile-multi==2.4.3 +colorama==0.4.6 + # via tox +pip-compile-multi==2.6.3 # via -r requirements/dev.in -pip-tools==6.5.1 +pip-tools==7.3.0 # via pip-compile-multi -toposort==1.7 +pyproject-api==1.6.1 + # via tox +pyproject-hooks==1.0.0 + # via build +toposort==1.10 # via pip-compile-multi -tox==3.24.5 +tox==4.11.3 # via -r requirements/dev.in -wheel==0.37.1 +wheel==0.41.2 # via pip-tools # The following packages are considered to be unsafe in a requirements file: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/requirements/docs.txt new/Flask-Login-0.6.3/requirements/docs.txt --- old/Flask-Login-0.6.2/requirements/docs.txt 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/requirements/docs.txt 2023-10-30 15:52:40.000000000 +0100 @@ -5,49 +5,51 @@ # # pip-compile-multi # -alabaster==0.7.12 +alabaster==0.7.13 # via sphinx -babel==2.9.1 +babel==2.13.1 # via sphinx -certifi==2021.10.8 +certifi==2023.7.22 # via requests -charset-normalizer==2.0.12 +charset-normalizer==3.3.1 # via requests -docutils==0.17.1 +docutils==0.20.1 # via sphinx -idna==3.3 +idna==3.4 # via requests -imagesize==1.3.0 +imagesize==1.4.1 # via sphinx -jinja2==3.1.1 +jinja2==3.1.2 # via sphinx -markupsafe==2.1.1 +markupsafe==2.1.3 # via jinja2 -packaging==21.3 +packaging==23.2 # via sphinx -pygments==2.11.2 +pygments==2.16.1 # via sphinx -pyparsing==3.0.7 - # via packaging -pytz==2022.1 - # via babel -requests==2.27.1 +requests==2.31.0 # via sphinx snowballstemmer==2.2.0 # via sphinx -sphinx==4.5.0 - # via -r requirements/docs.in -sphinxcontrib-applehelp==1.0.2 +sphinx==7.2.6 + # via + # -r requirements/docs.in + # sphinxcontrib-applehelp + # sphinxcontrib-devhelp + # sphinxcontrib-htmlhelp + # sphinxcontrib-qthelp + # sphinxcontrib-serializinghtml +sphinxcontrib-applehelp==1.0.7 # via sphinx -sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-devhelp==1.0.5 # via sphinx -sphinxcontrib-htmlhelp==2.0.0 +sphinxcontrib-htmlhelp==2.0.4 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-qthelp==1.0.6 # via sphinx -sphinxcontrib-serializinghtml==1.1.5 +sphinxcontrib-serializinghtml==1.1.9 # via sphinx -urllib3==1.26.9 +urllib3==2.0.7 # via requests diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/requirements/style.txt new/Flask-Login-0.6.3/requirements/style.txt --- old/Flask-Login-0.6.2/requirements/style.txt 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/requirements/style.txt 2023-10-30 15:52:40.000000000 +0100 @@ -5,25 +5,24 @@ # # pip-compile-multi # -cfgv==3.3.1 +cfgv==3.4.0 # via pre-commit -distlib==0.3.4 +distlib==0.3.7 # via virtualenv -filelock==3.6.0 +filelock==3.13.0 # via virtualenv -identify==2.4.12 +identify==2.5.31 # via pre-commit -nodeenv==1.6.0 +nodeenv==1.8.0 # via pre-commit -platformdirs==2.5.1 +platformdirs==3.11.0 # via virtualenv -pre-commit==2.17.0 +pre-commit==3.5.0 # via -r requirements/style.in -pyyaml==6.0 +pyyaml==6.0.1 # via pre-commit -six==1.16.0 - # via virtualenv -toml==0.10.2 - # via pre-commit -virtualenv==20.14.0 +virtualenv==20.24.6 # via pre-commit + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/requirements/tests.txt new/Flask-Login-0.6.3/requirements/tests.txt --- old/Flask-Login-0.6.2/requirements/tests.txt 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/requirements/tests.txt 2023-10-30 15:52:40.000000000 +0100 @@ -5,27 +5,19 @@ # # pip-compile-multi # -asgiref==3.5.0 +asgiref==3.7.2 # via -r requirements/tests.in -attrs==21.4.0 - # via pytest -blinker==1.4 +blinker==1.6.3 # via -r requirements/tests.in -coverage==6.3.2 +coverage==7.3.2 # via -r requirements/tests.in -iniconfig==1.1.1 - # via pytest -packaging==21.3 +iniconfig==2.0.0 # via pytest -pluggy==1.0.0 +packaging==23.2 # via pytest -py==1.11.0 +pluggy==1.3.0 # via pytest -pyparsing==3.0.7 - # via packaging -pytest==7.1.1 +pytest==7.4.3 # via -r requirements/tests.in -semantic-version==2.9.0 +semantic-version==2.10.0 # via -r requirements/tests.in -tomli==2.0.1 - # via pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/src/Flask_Login.egg-info/PKG-INFO new/Flask-Login-0.6.3/src/Flask_Login.egg-info/PKG-INFO --- old/Flask-Login-0.6.2/src/Flask_Login.egg-info/PKG-INFO 2022-07-25 17:15:38.000000000 +0200 +++ new/Flask-Login-0.6.3/src/Flask_Login.egg-info/PKG-INFO 2023-10-30 15:53:07.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: Flask-Login -Version: 0.6.2 +Version: 0.6.3 Summary: User authentication and session management for Flask. Home-page: https://github.com/maxcountryman/flask-login Author: Matthew Frazier @@ -23,6 +23,8 @@ Requires-Python: >=3.7 Description-Content-Type: text/markdown License-File: LICENSE +Requires-Dist: Flask>=1.0.4 +Requires-Dist: Werkzeug>=1.0.1 # Flask-Login diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/src/flask_login/__about__.py new/Flask-Login-0.6.3/src/flask_login/__about__.py --- old/Flask-Login-0.6.2/src/flask_login/__about__.py 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/src/flask_login/__about__.py 2023-10-30 15:52:40.000000000 +0100 @@ -1,7 +1,7 @@ __title__ = "Flask-Login" __description__ = "User session management for Flask" __url__ = "https://github.com/maxcountryman/flask-login" -__version_info__ = ("0", "6", "2") +__version_info__ = ("0", "6", "3") __version__ = ".".join(__version_info__) __author__ = "Matthew Frazier" __author_email__ = "[email protected]" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/src/flask_login/utils.py new/Flask-Login-0.6.3/src/flask_login/utils.py --- old/Flask-Login-0.6.2/src/flask_login/utils.py 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/src/flask_login/utils.py 2023-10-30 15:52:40.000000000 +0100 @@ -1,8 +1,10 @@ import hmac from functools import wraps from hashlib import sha512 -from urllib.parse import urlparse -from urllib.parse import urlunparse +from urllib.parse import parse_qs +from urllib.parse import urlencode +from urllib.parse import urlsplit +from urllib.parse import urlunsplit from flask import current_app from flask import g @@ -11,8 +13,6 @@ from flask import session from flask import url_for from werkzeug.local import LocalProxy -from werkzeug.urls import url_decode -from werkzeug.urls import url_encode from .config import COOKIE_NAME from .config import EXEMPT_METHODS @@ -73,13 +73,13 @@ :param current_url: The URL to reduce. :type current_url: str """ - l_url = urlparse(login_url) - c_url = urlparse(current_url) + l_url = urlsplit(login_url) + c_url = urlsplit(current_url) if (not l_url.scheme or l_url.scheme == c_url.scheme) and ( not l_url.netloc or l_url.netloc == c_url.netloc ): - return urlunparse(("", "", c_url.path, c_url.params, c_url.query, "")) + return urlunsplit(("", "", c_url.path, c_url.query, "")) return current_url @@ -122,14 +122,14 @@ if next_url is None: return base - parsed_result = urlparse(base) - md = url_decode(parsed_result.query) + parsed_result = urlsplit(base) + md = parse_qs(parsed_result.query, keep_blank_values=True) md[next_field] = make_next_param(base, next_url) netloc = current_app.config.get("FORCE_HOST_FOR_REDIRECTS") or parsed_result.netloc parsed_result = parsed_result._replace( - netloc=netloc, query=url_encode(md, sort=True) + netloc=netloc, query=urlencode(md, doseq=True) ) - return urlunparse(parsed_result) + return urlunsplit(parsed_result) def login_fresh(): @@ -349,14 +349,12 @@ num_login_views = len(current_app.login_manager.blueprint_login_views) if blueprint is not None or num_login_views != 0: - (current_app.login_manager.blueprint_login_views[blueprint.name]) = login_view if ( current_app.login_manager.login_view is not None and None not in current_app.login_manager.blueprint_login_views ): - ( current_app.login_manager.blueprint_login_views[None] ) = current_app.login_manager.login_view diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/Flask-Login-0.6.2/tests/test_login.py new/Flask-Login-0.6.3/tests/test_login.py --- old/Flask-Login-0.6.2/tests/test_login.py 2022-07-25 17:15:17.000000000 +0200 +++ new/Flask-Login-0.6.3/tests/test_login.py 2023-10-30 15:52:40.000000000 +0100 @@ -1,9 +1,13 @@ +from __future__ import annotations + import sys import unittest from collections.abc import Hashable from contextlib import contextmanager +from dataclasses import dataclass from datetime import datetime from datetime import timedelta +from datetime import timezone from unittest.mock import ANY from unittest.mock import Mock from unittest.mock import patch @@ -54,6 +58,7 @@ from flask_login.utils import _user_context_processor from semantic_version import Version from werkzeug.middleware.proxy_fix import ProxyFix +from werkzeug.test import Client sys_version = Version( major=sys.version_info.major, @@ -61,6 +66,55 @@ patch=sys.version_info.micro, ) +# Support Werkzeug < 2.3 +new_cookie_methods = hasattr(Client, "get_cookie") + + +@dataclass +class BasicCookie: + key: str + value: str + expires: datetime | None + + +def client_get_cookie( + client: Client, key: str, domain: str = "localhost", path: str = "/" +) -> BasicCookie | None: + if new_cookie_methods: + if domain.startswith("."): + domain = domain[1:] + + cookie = client.get_cookie(key, domain, path) + + if cookie is None: + return None + + return BasicCookie(cookie.key, cookie.value, cookie.expires) + else: + try: + cookie = client.cookie_jar._cookies[domain][path][key] + + if cookie.expires is None: + expires = None + else: + expires = datetime.fromtimestamp(cookie.expires, timezone.utc) + + return BasicCookie(cookie.name, cookie.value, expires) + except KeyError: + return None + + +def client_set_cookie( + client: Client, key: str, value: str, domain: str | None = None +) -> None: + if new_cookie_methods: + if domain.startswith("."): + domain = domain[1:] + + client.set_cookie(key, value, domain=domain) + else: + client.set_cookie(domain, key, value) + @contextmanager def listen_to(signal): @@ -528,7 +582,6 @@ def test_unauthorized_uses_blueprint_login_view(self): with self.app.app_context(): - first = Blueprint("first", "first") second = Blueprint("second", "second") @@ -567,7 +620,6 @@ set_login_view("second_login", blueprint=second) with self.app.test_client() as c: - result = c.get("/protected") self.assertEqual(result.status_code, 302) expected = "/app_login?next=%2Fprotected" @@ -598,7 +650,6 @@ set_login_view("app_login") with self.app.test_client() as c: - result = c.get("/protected") self.assertEqual(result.status_code, 302) expected = "/app_login?next=%2Fprotected" @@ -670,32 +721,15 @@ duration = self.app.config["REMEMBER_COOKIE_DURATION"] = timedelta(days=2) path = self.app.config["REMEMBER_COOKIE_PATH"] = "/mypath" domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local" - - with self.app.test_client() as c: - c.get("/login-notch-remember") - - # TODO: Is there a better way to test this? - self.assertIn( - domain, - c.cookie_jar._cookies, - "Custom domain not found as cookie domain", - ) - domain_cookie = c.cookie_jar._cookies[domain] - self.assertIn(path, domain_cookie, "Custom path not found as cookie path") - path_cookie = domain_cookie[path] - self.assertIn(name, path_cookie, "Custom name not found as cookie name") - cookie = path_cookie[name] - - expiration_date = datetime.utcfromtimestamp(cookie.expires) - expected_date = datetime.utcnow() + duration - difference = expected_date - expiration_date - - fail_msg = ( - f"The expiration date {expiration_date} was far from" - f" the expected {expected_date}" - ) - self.assertLess(difference, timedelta(seconds=10), fail_msg) - self.assertGreater(difference, timedelta(seconds=-10), fail_msg) + c = self.app.test_client() + c.get("/login-notch-remember") + cookie = client_get_cookie(c, name, domain, path) + self.assertIsNotNone(cookie) + self.assertIsNotNone(cookie.expires) + expected_date = datetime.now(timezone.utc) + duration + difference = expected_date - cookie.expires + self.assertLess(difference, timedelta(seconds=10)) + self.assertGreater(difference, timedelta(seconds=-10)) def test_remember_me_custom_duration_uses_custom_cookie(self): name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname" @@ -703,55 +737,31 @@ duration = timedelta(hours=7) path = self.app.config["REMEMBER_COOKIE_PATH"] = "/mypath" domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local" - - with self.app.test_client() as c: - c.get("/login-notch-remember-custom") - - # TODO: Is there a better way to test this? - self.assertIn( - domain, - c.cookie_jar._cookies, - "Custom domain not found as cookie domain", - ) - domain_cookie = c.cookie_jar._cookies[domain] - self.assertIn(path, domain_cookie, "Custom path not found as cookie path") - path_cookie = domain_cookie[path] - self.assertIn(name, path_cookie, "Custom name not found as cookie name") - cookie = path_cookie[name] - - expiration_date = datetime.utcfromtimestamp(cookie.expires) - expected_date = datetime.utcnow() + duration - difference = expected_date - expiration_date - - fail_msg = ( - f"The expiration date {expiration_date} was far from" - f" the expected {expected_date}" - ) - self.assertLess(difference, timedelta(seconds=10), fail_msg) - self.assertGreater(difference, timedelta(seconds=-10), fail_msg) + c = self.app.test_client() + c.get("/login-notch-remember-custom") + cookie = client_get_cookie(c, name, domain, path) + self.assertIsNotNone(cookie) + self.assertIsNotNone(cookie.expires) + expected_date = datetime.now(timezone.utc) + duration + difference = expected_date - cookie.expires + self.assertLess(difference, timedelta(seconds=10)) + self.assertGreater(difference, timedelta(seconds=-10)) def test_remember_me_accepts_duration_as_int(self): self.app.config["REMEMBER_COOKIE_DURATION"] = 172800 duration = timedelta(seconds=172800) name = self.app.config["REMEMBER_COOKIE_NAME"] = "myname" domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local" - - with self.app.test_client() as c: - result = c.get("/login-notch-remember") - self.assertEqual(result.status_code, 200) - - cookie = c.cookie_jar._cookies[domain]["/"][name] - - expiration_date = datetime.utcfromtimestamp(cookie.expires) - expected_date = datetime.utcnow() + duration - difference = expected_date - expiration_date - - fail_msg = ( - f"The expiration date {expiration_date} was far from" - f" the expected {expected_date}" - ) - self.assertLess(difference, timedelta(seconds=10), fail_msg) - self.assertGreater(difference, timedelta(seconds=-10), fail_msg) + c = self.app.test_client() + result = c.get("/login-notch-remember") + self.assertEqual(result.status_code, 200) + cookie = client_get_cookie(c, name, domain) + self.assertIsNotNone(cookie) + self.assertIsNotNone(cookie.expires) + expected_date = datetime.now(timezone.utc) + duration + difference = expected_date - cookie.expires + self.assertLess(difference, timedelta(seconds=10)) + self.assertGreater(difference, timedelta(seconds=-10)) def test_remember_me_with_invalid_duration_returns_500_response(self): self.app.config["REMEMBER_COOKIE_DURATION"] = "123" @@ -793,52 +803,35 @@ ) self.assertIn(expected_exception_message, str(cm.exception)) - def test_remember_me_refresh_every_request(self): + def test_remember_me_no_refresh_every_request(self): domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local" path = self.app.config["REMEMBER_COOKIE_PATH"] = "/" - - # No refresh self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = False - with self.app.test_client() as c: - c.get("/login-notch-remember") - self.assertIn("remember", c.cookie_jar._cookies[domain][path]) - expiration_date_1 = datetime.utcfromtimestamp( - c.cookie_jar._cookies[domain][path]["remember"].expires - ) + c = self.app.test_client() + c.get("/login-notch-remember") + cookie1 = client_get_cookie(c, "remember", domain, path) + self.assertIsNotNone(cookie1.expires) + self._delete_session(c) + c.get("/username") + cookie2 = client_get_cookie(c, "remember", domain, path) + self.assertEqual(cookie1.expires, cookie2.expires) - self._delete_session(c) - - c.get("/username") - self.assertIn("remember", c.cookie_jar._cookies[domain][path]) - expiration_date_2 = datetime.utcfromtimestamp( - c.cookie_jar._cookies[domain][path]["remember"].expires - ) - self.assertEqual(expiration_date_1, expiration_date_2) - - # With refresh (mock datetime's `utcnow`) + def test_remember_me_refresh_each_request(self): with patch("flask_login.login_manager.datetime") as mock_dt: - self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = True now = datetime.utcnow() mock_dt.utcnow = Mock(return_value=now) - with self.app.test_client() as c: - c.get("/login-notch-remember") - self.assertIn("remember", c.cookie_jar._cookies[domain][path]) - expiration_date_1 = datetime.utcfromtimestamp( - c.cookie_jar._cookies[domain][path]["remember"].expires - ) - self.assertIsNotNone(expiration_date_1) - - self._delete_session(c) - - mock_dt.utcnow = Mock(return_value=now + timedelta(seconds=1)) - c.get("/username") - self.assertIn("remember", c.cookie_jar._cookies[domain][path]) - expiration_date_2 = datetime.utcfromtimestamp( - c.cookie_jar._cookies[domain][path]["remember"].expires - ) - self.assertIsNotNone(expiration_date_2) - self.assertNotEqual(expiration_date_1, expiration_date_2) + domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local" + path = self.app.config["REMEMBER_COOKIE_PATH"] = "/" + self.app.config["REMEMBER_COOKIE_REFRESH_EACH_REQUEST"] = True + c = self.app.test_client() + c.get("/login-notch-remember") + cookie1 = client_get_cookie(c, "remember", domain, path) + self.assertIsNotNone(cookie1.expires) + mock_dt.utcnow.return_value = now + timedelta(seconds=1) + c.get("/username") + cookie2 = client_get_cookie(c, "remember", domain, path) + self.assertNotEqual(cookie1.expires, cookie2.expires) def test_remember_me_is_unfresh(self): with self.app.test_client() as c: @@ -1012,13 +1005,15 @@ def test_invalid_remember_cookie(self): domain = self.app.config["REMEMBER_COOKIE_DOMAIN"] = ".localhost.local" - with self.app.test_client() as c: - c.get("/login-notch-remember") - with c.session_transaction() as sess: - sess["_user_id"] = None - c.set_cookie(domain, self.remember_cookie_name, "foo") - result = c.get("/username") - self.assertEqual("Anonymous", result.data.decode("utf-8")) + c = self.app.test_client() + c.get("/login-notch-remember") + + with c.session_transaction() as sess: + sess["_user_id"] = None + + client_set_cookie(c, self.remember_cookie_name, "foo", domain=domain) + result = c.get("/username") + self.assertEqual("Anonymous", result.data.decode("utf-8")) # # Session Protection
