diff --git a/.gitignore b/.gitignore index 615e8a8f..1c1ae95b 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,4 @@ changelog.sh .python-version files/local.env +.venv/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a56a54..02530b28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed - Uses Django native JSON field +- Introduce configuration to request POST only requests for social authentication ## [5.2.0](https://github.com/python-social-auth/social-app-django/releases/tag/5.2.0) - 2023-03-31 diff --git a/social_django/utils.py b/social_django/utils.py index 7c0ccce8..1e66c867 100644 --- a/social_django/utils.py +++ b/social_django/utils.py @@ -3,6 +3,7 @@ from django.conf import settings from django.http import Http404 from django.urls import reverse +from django.views.decorators.http import require_POST from social_core.exceptions import MissingBackend from social_core.utils import get_strategy, module_member, setting_name @@ -12,6 +13,8 @@ STORAGE = getattr( settings, setting_name("STORAGE"), "social_django.models.DjangoStorage" ) +REQUIRE_POST = setting_name("REQUIRE_POST") + Strategy = module_member(STRATEGY) Storage = module_member(STORAGE) @@ -48,3 +51,15 @@ def wrapper(request, backend, *args, **kwargs): return wrapper return decorator + + +def maybe_require_post(func): + @wraps(func) + def wrapper(request, backend, *args, **kwargs): + require_post = getattr(settings, REQUIRE_POST, False) + if require_post: + return require_POST(func)(request, backend, *args, **kwargs) + + return func(request, backend, *args, **kwargs) + + return wrapper diff --git a/social_django/views.py b/social_django/views.py index bbddc5b9..641c2200 100644 --- a/social_django/views.py +++ b/social_django/views.py @@ -7,7 +7,7 @@ from social_core.actions import do_auth, do_complete, do_disconnect from social_core.utils import setting_name -from .utils import psa +from .utils import maybe_require_post, psa NAMESPACE = getattr(settings, setting_name("URL_NAMESPACE"), None) or "social" @@ -17,6 +17,7 @@ @never_cache +@maybe_require_post @psa(f"{NAMESPACE}:complete") def auth(request, backend): return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME) diff --git a/tests/test_views.py b/tests/test_views.py index aa33aeed..7f934019 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -26,6 +26,13 @@ def test_begin_view(self): response = self.client.get(url) self.assertEqual(response.status_code, 404) + def test_require_post_works(self): + with override_settings(SOCIAL_AUTH_REQUIRE_POST=True): + response = self.client.get( + reverse("social:begin", kwargs={"backend": "facebook"}) + ) + self.assertEqual(response.status_code, 405) + @mock.patch("social_core.backends.base.BaseAuth.request") def test_complete(self, mock_request): url = reverse("social:complete", kwargs={"backend": "facebook"})