Skip to content

Commit

Permalink
Merge pull request #136 from atdsaa/custom_error_handler
Browse files Browse the repository at this point in the history
Custom error handler
  • Loading branch information
JonasKs committed Jan 25, 2021
2 parents 1474cf7 + 70e47fe commit 7c12aac
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 13 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

`1.6.0`_ - 2021-01-25
---------------------

**Features**

* New parameter called `CUSTOM_FAILED_RESPONSE_VIEW`, allowing you to set a custom django function view to handle login
failures. #136


`1.5.0`_ - 2021-01-18
---------------------
Expand Down Expand Up @@ -219,6 +227,7 @@ Changelog

* Initial release

.. _1.6.0: https://github.com/snok/django-auth-adfs/compare/1.5.0...1.6.0
.. _1.5.0: https://github.com/jobec/django-auth-adfs/compare/1.4.1...1.5.0
.. _1.4.1: https://github.com/jobec/django-auth-adfs/compare/1.4.0...1.4.1
.. _1.4.0: https://github.com/jobec/django-auth-adfs/compare/1.3.1...1.4.0
Expand Down
2 changes: 1 addition & 1 deletion django_auth_adfs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
Adding imports here will break setup.py
"""

__version__ = '1.5.0'
__version__ = '1.6.0'
9 changes: 9 additions & 0 deletions django_auth_adfs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
from django.contrib.auth import get_user_model
from django.core.exceptions import ImproperlyConfigured
from django.http import QueryDict
from django.shortcuts import render
from django.utils.module_loading import import_string

try:
from django.urls import reverse
except ImportError: # Django < 1.10
from django.core.urlresolvers import reverse


logger = logging.getLogger("django_auth_adfs")

AZURE_AD_SERVER = "login.microsoftonline.com"
Expand Down Expand Up @@ -69,6 +71,9 @@ def __init__(self):
self.TIMEOUT = 5
self.USERNAME_CLAIM = "winaccountname"
self.JWT_LEEWAY = 0
self.CUSTOM_FAILED_RESPONSE_VIEW = lambda request, error_message, status: render(
request, 'django_auth_adfs/login_failed.html', {'error_message': error_message}, status=status
)

required_settings = [
"AUDIENCE",
Expand Down Expand Up @@ -151,6 +156,10 @@ def __init__(self):
msg = "django_auth_adfs setting '{0}' has not been set".format(setting)
raise ImproperlyConfigured(msg)

# Setup dynamic settings
if not callable(self.CUSTOM_FAILED_RESPONSE_VIEW):
self.CUSTOM_FAILED_RESPONSE_VIEW = import_string(self.CUSTOM_FAILED_RESPONSE_VIEW)

# Validate setting conflicts
usermodel = get_user_model()
if usermodel.USERNAME_FIELD in self.CLAIM_MAPPING.keys():
Expand Down
28 changes: 17 additions & 11 deletions django_auth_adfs/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

from django.conf import settings as django_settings
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import redirect, render
from django.shortcuts import redirect
from django.utils.http import is_safe_url
from django.views.generic import View

from django_auth_adfs.config import provider_config
from django_auth_adfs.config import provider_config, settings
from django_auth_adfs.exceptions import MFARequired

logger = logging.getLogger("django_auth_adfs")
Expand All @@ -25,9 +25,11 @@ def get(self, request):
code = request.GET.get("code")
if not code:
# Return an error message
return render(request, 'django_auth_adfs/login_failed.html', {
'error_message': "No authorization code was provided.",
}, status=400)
return settings.CUSTOM_FAILED_RESPONSE_VIEW(
request,
error_message="No authorization code was provided.",
status=400
)

redirect_to = request.GET.get("state")
try:
Expand All @@ -54,14 +56,18 @@ def get(self, request):
return redirect(redirect_to)
else:
# Return a 'disabled account' error message
return render(request, 'django_auth_adfs/login_failed.html', {
'error_message': "Your account is disabled.",
}, status=403)
return settings.CUSTOM_FAILED_RESPONSE_VIEW(
request,
error_message="Your account is disabled.",
status=403
)
else:
# Return an 'invalid login' error message
return render(request, 'django_auth_adfs/login_failed.html', {
'error_message': "Login failed.",
}, status=401)
return settings.CUSTOM_FAILED_RESPONSE_VIEW(
request,
error_message="Login failed.",
status=401
)


class OAuth2LoginView(View):
Expand Down
3 changes: 3 additions & 0 deletions docs/install.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ In your project's ``settings.py`` add these settings.
'django_auth_adfs.middleware.LoginRequiredMiddleware',
)
# You can point login failures to a custom Django function based view for customization of the UI
CUSTOM_FAILED_RESPONSE_VIEW = 'dot.path.to.custom.views.login_failed'
In your project's ``urls.py`` add these paths:
.. code-block:: python
Expand Down
18 changes: 18 additions & 0 deletions docs/settings_ref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,24 @@ Allows you to set a leeway of the JWT token. See the official
`PyJWT <https://pyjwt.readthedocs.io/en/stable/usage.html>`__ docs for more information.


CUSTOM_FAILED_RESPONSE_VIEW
--------------------------------
* **Default**: ``lambda``
* **Type**: ``str`` or ``callable``

Allows you to set a custom django function view to handle login failures. Can be a dot path to your
Django function based view function or a callable.

Callable must have the following method signature accepting ``error_message`` and ``status`` arguments:

.. code-block:: python
def failed_response(request, error_message, status):
# Return an error message
return render(request, 'myapp/login_failed.html', {
'error_message': error_message,
}, status=status)
GROUP_CLAIM
-----------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = 'django-auth-adfs'
version = '1.5.0' # Remember to also change __init__.py version
version = '1.6.0' # Remember to also change __init__.py version
description = 'A Django authentication backend for Microsoft ADFS and AzureAD'
authors = ['Joris Beckers <joris.beckers@gmail.com>']
maintainers = ['Jonas Krüger Svensson <jonas-ks@hotmail.com>', 'Sondre Lillebø Gundersen <sondrelg@live.no>']
Expand Down
13 changes: 13 additions & 0 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ def test_required_setting(self):
with patch("django_auth_adfs.config.django_settings", settings):
self.assertRaises(ImproperlyConfigured, Settings)

def test_default_failed_response_setting(self):
settings = deepcopy(django_settings)
with patch("django_auth_adfs.config.django_settings", settings):
s = Settings()
self.assertTrue(callable(s.CUSTOM_FAILED_RESPONSE_VIEW))

def test_dotted_path_failed_response_setting(self):
settings = deepcopy(django_settings)
settings.AUTH_ADFS["CUSTOM_FAILED_RESPONSE_VIEW"] = 'tests.views.test_failed_response'
with patch("django_auth_adfs.config.django_settings", settings):
s = Settings()
self.assertTrue(callable(s.CUSTOM_FAILED_RESPONSE_VIEW))


class CustomSettingsTests(SimpleTestCase):
def setUp(self):
Expand Down
2 changes: 2 additions & 0 deletions tests/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def test_failed_response(request, error_message, status):
pass

0 comments on commit 7c12aac

Please sign in to comment.