Skip to content

Commit 2150497

Browse files
authored
Merge pull request #410 from g-as/feature/various_updates
Python x Django updates
2 parents d2fdb44 + d6c0129 commit 2150497

File tree

11 files changed

+70
-69
lines changed

11 files changed

+70
-69
lines changed

.github/workflows/python-package.yml

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,13 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ["3.8", "3.9", "3.10", "3.11"]
19-
django-version: ["3.2", "4.1", "4.2", "5.0"]
18+
python-version: ["3.10", "3.11", "3.12"]
19+
django-version: ["4.2", "5.0", "5.1"]
2020
include:
21-
- python-version: "3.12"
22-
django-version: "4.2"
23-
- python-version: "3.12"
24-
django-version: "5.0"
25-
exclude:
26-
- python-version: "3.11"
27-
django-version: "3.2"
28-
- python-version: "3.8"
29-
django-version: "5.0"
3021
- python-version: "3.9"
31-
django-version: "5.0"
22+
django-version: "4.2"
23+
- python-version: "3.13"
24+
django-version: "5.1"
3225

3326
steps:
3427
- uses: actions/checkout@v4

.pre-commit-config.yaml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ exclude: 'docs|migrations'
22

33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.3.0
5+
rev: v5.0.0
66
hooks:
77
- id: trailing-whitespace
88
- id: end-of-file-fixer
@@ -13,37 +13,37 @@ repos:
1313
- id: debug-statements
1414

1515
- repo: https://github.com/asottile/pyupgrade
16-
rev: v2.34.0
16+
rev: v3.19.0
1717
hooks:
1818
- id: pyupgrade
19-
args: [--py37-plus]
19+
args: [--py39-plus]
2020

2121
- repo: https://github.com/myint/autoflake
22-
rev: 'v1.4'
22+
rev: 'v2.3.1'
2323
hooks:
2424
- id: autoflake
2525
args: ['--in-place', '--remove-all-unused-imports', '--ignore-init-module-imports']
2626

2727
- repo: https://github.com/pycqa/isort
28-
rev: 5.10.1
28+
rev: 5.13.2
2929
hooks:
3030
- id: isort
3131
name: isort (python)
3232
args: ['--settings-path=pyproject.toml']
3333

3434
- repo: https://github.com/psf/black
35-
rev: 22.6.0
35+
rev: 24.10.0
3636
hooks:
3737
- id: black
3838

3939
- repo: https://github.com/adamchainz/django-upgrade
40-
rev: 1.7.0
40+
rev: 1.22.2
4141
hooks:
4242
- id: django-upgrade
43-
args: [--target-version, "3.2"]
43+
args: [--target-version, "4.2"]
4444

4545
- repo: https://github.com/pycqa/flake8
46-
rev: 4.0.1
46+
rev: 7.1.1
4747
hooks:
4848
- id: flake8
4949
args: ['--config=setup.cfg']

djangosaml2/backends.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import logging
1717
import warnings
18-
from typing import Any, Optional, Tuple
18+
from typing import Any, Optional
1919

2020
from django.apps import apps
2121
from django.conf import settings
@@ -72,7 +72,7 @@ def _user_lookup_attribute(self) -> str:
7272

7373
def _extract_user_identifier_params(
7474
self, session_info: dict, attributes: dict, attribute_mapping: dict
75-
) -> Tuple[str, Optional[Any]]:
75+
) -> tuple[str, Optional[Any]]:
7676
"""Returns the attribute to perform a user lookup on, and the value to use for it.
7777
The value could be the name_id, or any other saml attribute from the request.
7878
"""
@@ -262,7 +262,7 @@ def get_or_create_user(
262262
attributes: dict,
263263
attribute_mapping: dict,
264264
request,
265-
) -> Tuple[Optional[settings.AUTH_USER_MODEL], bool]:
265+
) -> tuple[Optional[settings.AUTH_USER_MODEL], bool]:
266266
"""Look up the user to authenticate. If he doesn't exist, this method creates him (if so desired).
267267
The default implementation looks only at the user_identifier. Override this method in order to do more complex behaviour,
268268
e.g. customize this per IdP.
@@ -322,27 +322,31 @@ def get_attribute_value(self, django_field, attributes, attribute_mapping):
322322
warnings.warn(
323323
"get_attribute_value() is deprecated, look at the Saml2Backend on how to subclass it",
324324
DeprecationWarning,
325+
stacklevel=2,
325326
)
326327
return self._get_attribute_value(django_field, attributes, attribute_mapping)
327328

328329
def get_django_user_main_attribute(self):
329330
warnings.warn(
330331
"get_django_user_main_attribute() is deprecated, look at the Saml2Backend on how to subclass it",
331332
DeprecationWarning,
333+
stacklevel=2,
332334
)
333335
return self._user_lookup_attribute
334336

335337
def get_django_user_main_attribute_lookup(self):
336338
warnings.warn(
337339
"get_django_user_main_attribute_lookup() is deprecated, look at the Saml2Backend on how to subclass it",
338340
DeprecationWarning,
341+
stacklevel=2,
339342
)
340343
return getattr(settings, "SAML_DJANGO_USER_MAIN_ATTRIBUTE_LOOKUP", "")
341344

342345
def get_user_query_args(self, main_attribute):
343346
warnings.warn(
344347
"get_user_query_args() is deprecated, look at the Saml2Backend on how to subclass it",
345348
DeprecationWarning,
349+
stacklevel=2,
346350
)
347351
return {
348352
self.get_django_user_main_attribute()
@@ -353,20 +357,23 @@ def configure_user(self, user, attributes, attribute_mapping):
353357
warnings.warn(
354358
"configure_user() is deprecated, look at the Saml2Backend on how to subclass it",
355359
DeprecationWarning,
360+
stacklevel=2,
356361
)
357362
return self._update_user(user, attributes, attribute_mapping)
358363

359364
def update_user(self, user, attributes, attribute_mapping, force_save=False):
360365
warnings.warn(
361366
"update_user() is deprecated, look at the Saml2Backend on how to subclass it",
362367
DeprecationWarning,
368+
stacklevel=2,
363369
)
364370
return self._update_user(user, attributes, attribute_mapping)
365371

366372
def _set_attribute(self, obj, attr, value):
367373
warnings.warn(
368374
"_set_attribute() is deprecated, look at the Saml2Backend on how to subclass it",
369375
DeprecationWarning,
376+
stacklevel=2,
370377
)
371378
return set_attribute(obj, attr, value)
372379

@@ -375,5 +382,6 @@ def get_saml_user_model():
375382
warnings.warn(
376383
"_set_attribute() is deprecated, look at the Saml2Backend on how to subclass it",
377384
DeprecationWarning,
385+
stacklevel=2,
378386
)
379387
return Saml2Backend()._user_model

djangosaml2/overrides.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ class Saml2Client(saml2.client.Saml2Client):
1919
def do_logout(self, *args, **kwargs):
2020
if not kwargs.get("expected_binding"):
2121
try:
22-
kwargs[
23-
"expected_binding"
24-
] = settings.SAML_LOGOUT_REQUEST_PREFERRED_BINDING
22+
kwargs["expected_binding"] = (
23+
settings.SAML_LOGOUT_REQUEST_PREFERRED_BINDING
24+
)
2525
except AttributeError:
2626
logger.warning(
2727
"SAML_LOGOUT_REQUEST_PREFERRED_BINDING setting is"

djangosaml2/tests/__init__.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,9 @@ def add_outstanding_query(self, session_id, came_from):
139139
came_from,
140140
)
141141
self.saml_session.save()
142-
self.client.cookies[
143-
settings.SESSION_COOKIE_NAME
144-
] = self.saml_session.session_key
142+
self.client.cookies[settings.SESSION_COOKIE_NAME] = (
143+
self.saml_session.session_key
144+
)
145145

146146
def b64_for_post(self, xml_text, encoding="utf-8"):
147147
return base64.b64encode(xml_text.encode(encoding)).decode("ascii")
@@ -308,8 +308,12 @@ def test_unknown_idp(self):
308308
metadata_file="remote_metadata_three_idps.xml",
309309
)
310310

311-
response = self.client.get(reverse("saml2_login") + "?idp=<b>https://unknown.org</b>")
312-
self.assertContains(response, "&lt;b&gt;https://unknown.org&lt;/b&gt;", status_code=403)
311+
response = self.client.get(
312+
reverse("saml2_login") + "?idp=<b>https://unknown.org</b>"
313+
)
314+
self.assertContains(
315+
response, "&lt;b&gt;https://unknown.org&lt;/b&gt;", status_code=403
316+
)
313317

314318
def test_login_authn_context(self):
315319
sp_kwargs = {

djangosaml2/utils.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ def available_idps(config: SPConfig, langpref=None, idp_to_check: str = None) ->
4747
for metadata in config.metadata.metadata.values():
4848
# initiate a fetch to the selected idp when using MDQ, otherwise the MetaDataMDX is an empty database
4949
if isinstance(metadata, MetaDataMDX) and idp_to_check:
50-
m = metadata[idp_to_check]
50+
m = metadata[idp_to_check] # noqa: F841
5151
result = metadata.any("idpsso_descriptor", "single_sign_on_service")
5252
if result:
5353
idps.update(result.keys())
@@ -108,11 +108,7 @@ def validate_referral_url(request, url):
108108
# SAML_STRICT_URL_VALIDATION setting can be used to turn off this check.
109109
# This should only happen if there is no slash, host and/or protocol in the
110110
# given URL. A better fix would be to add those to the RelayState.
111-
saml_strict_url_validation = getattr(
112-
settings,
113-
"SAML_STRICT_URL_VALIDATION",
114-
True
115-
)
111+
saml_strict_url_validation = getattr(settings, "SAML_STRICT_URL_VALIDATION", True)
116112
try:
117113
if saml_strict_url_validation:
118114
# This will also resolve Django URL pattern names
@@ -133,8 +129,7 @@ def validate_referral_url(request, url):
133129
)
134130

135131
if not url_has_allowed_host_and_scheme(url=url, allowed_hosts=saml_allowed_hosts):
136-
logger.debug("Referral URL not in SAML_ALLOWED_HOSTS or of the origin "
137-
"host.")
132+
logger.debug("Referral URL not in SAML_ALLOWED_HOSTS or of the origin host.")
138133
return None
139134

140135
return url
@@ -210,7 +205,7 @@ def add_idp_hinting(request, http_response) -> bool:
210205
return False
211206

212207

213-
@lru_cache()
208+
@lru_cache
214209
def get_csp_handler():
215210
"""Returns a view decorator for CSP."""
216211

djangosaml2/views.py

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def get_state_client(self, request: HttpRequest):
135135
return state, client
136136

137137

138-
@method_decorator(saml2_csp_update, name='dispatch')
138+
@method_decorator(saml2_csp_update, name="dispatch")
139139
class LoginView(SPConfigMixin, View):
140140
"""SAML Authorization Request initiator.
141141
@@ -389,7 +389,7 @@ def get(self, request, *args, **kwargs):
389389
except TemplateDoesNotExist as e:
390390
logger.debug(
391391
f"TemplateDoesNotExist: [{self.post_binding_form_template}] - {e}",
392-
exc_info=True
392+
exc_info=True,
393393
)
394394

395395
if not http_response:
@@ -574,7 +574,7 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
574574
session_info,
575575
attribute_mapping,
576576
create_unknown_user,
577-
assertion_info
577+
assertion_info,
578578
)
579579
except PermissionDenied as e:
580580
return self.handle_acs_failure(
@@ -592,21 +592,21 @@ def post(self, request, attribute_mapping=None, create_unknown_user=None):
592592
if not relay_state:
593593
logger.debug(
594594
"RelayState is not a valid URL, redirecting to fallback: %s",
595-
relay_state
595+
relay_state,
596596
)
597597
return HttpResponseRedirect(get_fallback_login_redirect_url())
598598

599599
logger.debug("Redirecting to the RelayState: %s", relay_state)
600600
return HttpResponseRedirect(relay_state)
601601

602602
def authenticate_user(
603-
self,
604-
request,
605-
session_info,
606-
attribute_mapping,
607-
create_unknown_user,
608-
assertion_info
609-
):
603+
self,
604+
request,
605+
session_info,
606+
attribute_mapping,
607+
create_unknown_user,
608+
assertion_info,
609+
):
610610
"""Calls Django's authenticate method after the SAML response is verified"""
611611
logger.debug("Trying to authenticate the user. Session info: %s", session_info)
612612

@@ -685,7 +685,7 @@ def get(self, request, *args, **kwargs):
685685
)
686686

687687

688-
@method_decorator(saml2_csp_update, name='dispatch')
688+
@method_decorator(saml2_csp_update, name="dispatch")
689689
class LogoutInitView(LoginRequiredMixin, SPConfigMixin, View):
690690
"""SAML Logout Request initiator
691691
@@ -801,7 +801,9 @@ def do_logout_service(self, request, data, binding, *args, **kwargs):
801801
)
802802
except StatusError as e:
803803
response = None
804-
logger.warning(f"Error logging out from remote provider: {e}", exc_info=True)
804+
logger.warning(
805+
f"Error logging out from remote provider: {e}", exc_info=True
806+
)
805807
state.sync()
806808
return finish_logout(request, response)
807809

@@ -853,13 +855,16 @@ def finish_logout(request, response):
853855
return HttpResponseRedirect(next_path)
854856
elif settings.LOGOUT_REDIRECT_URL is not None:
855857
fallback_url = resolve_url(settings.LOGOUT_REDIRECT_URL)
856-
logger.debug("No valid RelayState found; Redirecting to "
857-
"LOGOUT_REDIRECT_URL")
858+
logger.debug(
859+
"No valid RelayState found; Redirecting to " "LOGOUT_REDIRECT_URL"
860+
)
858861
return HttpResponseRedirect(fallback_url)
859862
else:
860863
current_site = get_current_site(request)
861-
logger.debug("No valid RelayState or LOGOUT_REDIRECT_URL found, "
862-
"rendering fallback template.")
864+
logger.debug(
865+
"No valid RelayState or LOGOUT_REDIRECT_URL found, "
866+
"rendering fallback template."
867+
)
863868
return render(
864869
request,
865870
"registration/logged_out.html",

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.black]
22
force-exclude = '''/(migrations)/'''
3-
target-version = ["py36"]
3+
target-version = ["py39"]
44

55
[tool.isort]
66
src_paths = ["djangosaml2", "tests"]

setup.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,18 @@ def read(*rnames):
3535
"Development Status :: 5 - Production/Stable",
3636
"Environment :: Web Environment",
3737
"Framework :: Django",
38-
"Framework :: Django :: 3.2",
39-
"Framework :: Django :: 4.1",
4038
"Framework :: Django :: 4.2",
4139
"Framework :: Django :: 5.0",
40+
"Framework :: Django :: 5.1",
4241
"Intended Audience :: Developers",
4342
"License :: OSI Approved :: Apache Software License",
4443
"Operating System :: OS Independent",
4544
"Programming Language :: Python",
46-
"Programming Language :: Python :: 3.8",
4745
"Programming Language :: Python :: 3.9",
4846
"Programming Language :: Python :: 3.10",
4947
"Programming Language :: Python :: 3.11",
5048
"Programming Language :: Python :: 3.12",
49+
"Programming Language :: Python :: 3.13",
5150
"Topic :: Internet :: WWW/HTTP",
5251
"Topic :: Internet :: WWW/HTTP :: WSGI",
5352
"Topic :: Security",
@@ -63,5 +62,5 @@ def read(*rnames):
6362
packages=find_packages(exclude=["tests", "tests.*"]),
6463
include_package_data=True,
6564
zip_safe=False,
66-
install_requires=["defusedxml>=0.4.1", "Django>=3.2", "pysaml2>=6.5.1"],
65+
install_requires=["defusedxml>=0.4.1", "Django>=4.2", "pysaml2>=6.5.1"],
6766
)

tests/settings.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,6 @@
104104

105105
USE_I18N = True
106106

107-
USE_L10N = True
108-
109107
USE_TZ = True
110108

111109

0 commit comments

Comments
 (0)