Skip to content

Commit f1e4caf

Browse files
committed
Merge branch 'master' into ACS_customizable_0.18.2
# Conflicts: # djangosaml2/urls.py # djangosaml2/views.py
2 parents 79fffd4 + 521089e commit f1e4caf

File tree

25 files changed

+289
-278
lines changed

25 files changed

+289
-278
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,11 @@
44
*.sqp
55
build/
66
dist/
7+
_build/
8+
.pytest_cache
9+
.env
10+
env/
11+
venv
12+
tags
13+
.idea/
14+
.vscode/

.hgignore

Lines changed: 0 additions & 9 deletions
This file was deleted.

.travis.yml

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,31 @@ sudo: false
55

66
matrix:
77
include:
8-
- python: 2.7
9-
env: TOX_ENV=py27-django18
10-
- python: 2.7
11-
env: TOX_ENV=py27-django19
12-
- python: 3.5
13-
env: TOX_ENV=py35-django19
14-
- python: 2.7
15-
env: TOX_ENV=py27-django110
16-
- python: 3.5
17-
env: TOX_ENV=py35-django110
18-
- python: 2.7
19-
env: TOX_ENV=py27-django111
20-
- python: 3.5
21-
env: TOX_ENV=py35-django111
22-
- python: 3.6
23-
env: TOX_ENV=py36-django111
24-
- python: 3.5
25-
env: TOX_ENV=py35-django20
26-
- python: 3.6
27-
env: TOX_ENV=py36-django20
28-
- python: 3.7
29-
env: TOX_ENV=py37-django20
30-
- python: 3.5
31-
env: TOX_ENV=py35-django21
32-
- python: 3.6
33-
env: TOX_ENV=py36-django21
34-
- python: 3.7
35-
env: TOX_ENV=py37-django21
368
- python: 3.5
379
env: TOX_ENV=py35-django22
3810
- python: 3.6
3911
env: TOX_ENV=py36-django22
4012
- python: 3.7
4113
env: TOX_ENV=py37-django22
14+
- python: 3.8
15+
env: TOX_ENV=py38-django22
4216
- python: 3.6
4317
env: TOX_ENV=py36-django30
4418
- python: 3.7
4519
env: TOX_ENV=py37-django30
20+
- python: 3.8
21+
env: TOX_ENV=py38-django30
4622
- python: 3.6
4723
env: TOX_ENV=py36-djangomaster
4824
- python: 3.7
4925
env: TOX_ENV=py37-djangomaster
26+
- python: 3.8
27+
env: TOX_ENV=py38-djangomaster
5028
fast_finish: true
5129
allow_failures:
5230
- env: TOX_ENV=py36-djangomaster
5331
- env: TOX_ENV=py37-djangomaster
32+
- env: TOX_ENV=py38-djangomaster
5433

5534
addons:
5635
apt:

CHANGES

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ TBD
2424

2525
Thanks to plumdog
2626

27+
UNRELEASED
28+
----------
29+
- Allowed creating Users with multiple required fields.
30+
2731
0.17.1 (2018-07-16)
2832
----------
2933
- A 403 (permission denied) is now raised if a SAMLResponse is replayed, instead of 500.

README.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,24 @@ setting::
315315
SAML_CONFIG_LOADER = 'python.path.to.your.callable'
316316

317317

318+
Custom error handler
319+
....................
320+
321+
When an error occurs during the authentication flow, djangosaml2 will render
322+
a simple error page with an error message and status code. You can customize
323+
this behaviour by specifying the path to your own error handler in the settings:
324+
325+
SAML_ACS_FAILURE_RESPONSE_FUNCTION = 'python.path.to.your.view'
326+
327+
This should be a view which takes a request, optional exception which occured
328+
and status code, and returns a response to serve the user. E.g. The default
329+
implementation looks like this::
330+
331+
def template_failure(request, exception=None, **kwargs):
332+
""" Renders a simple template with an error message. """
333+
return render(request, 'djangosaml2/login_error.html', {'exception': exception}, status=kwargs.get('status', 403))
334+
335+
318336
User attributes
319337
---------------
320338

djangosaml2/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
default_app_config = 'djangosaml2.apps.DjangoSaml2Config'

djangosaml2/acs_failures.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,10 @@
33
# This module defines a set of useful ACS failure functions that are used to
44
# produce an output suitable for end user in case of SAML failure.
55
#
6-
from __future__ import unicode_literals
76

8-
from django.core.exceptions import PermissionDenied
97
from django.shortcuts import render
108

119

12-
def template_failure(request, status=403, **kwargs):
13-
""" Renders a SAML-specific template with general authentication error description. """
14-
return render(request, 'djangosaml2/login_error.html', status=status)
15-
16-
17-
def exception_failure(request, exc_class=PermissionDenied, **kwargs):
18-
""" Rather than using a custom SAML specific template that is rendered on failure,
19-
this makes use of a standard exception handling machinery present in Django
20-
and thus ends up rendering a project-wide error page for Permission Denied exceptions.
21-
"""
22-
raise exc_class
10+
def template_failure(request, exception=None, status=403, **kwargs):
11+
""" Renders a simple template with an error message. """
12+
return render(request, 'djangosaml2/login_error.html', {'exception': exception}, status=status)

djangosaml2/apps.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.apps import AppConfig
2+
3+
4+
class DjangoSaml2Config(AppConfig):
5+
name = 'djangosaml2'
6+
verbose_name = "DjangoSAML2"
7+
8+
def ready(self):
9+
from . import signals # noqa

djangosaml2/backends.py

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,28 @@
1818
from django.conf import settings
1919
from django.contrib import auth
2020
from django.contrib.auth.backends import ModelBackend
21-
from django.core.exceptions import (
22-
MultipleObjectsReturned, ImproperlyConfigured,
23-
)
24-
25-
from djangosaml2.signals import pre_user_save
21+
from django.core.exceptions import (ImproperlyConfigured,
22+
MultipleObjectsReturned)
2623

24+
from .signals import pre_user_save
2725

2826
logger = logging.getLogger('djangosaml2')
2927

3028

3129
def get_model(model_path):
30+
from django.apps import apps
3231
try:
33-
from django.apps import apps
3432
return apps.get_model(model_path)
35-
except ImportError:
36-
# Django < 1.7 (cannot use the new app loader)
37-
from django.db.models import get_model as django_get_model
38-
try:
39-
app_label, model_name = model_path.split('.')
40-
except ValueError:
41-
raise ImproperlyConfigured("SAML_USER_MODEL must be of the form "
42-
"'app_label.model_name'")
43-
user_model = django_get_model(app_label, model_name)
44-
if user_model is None:
45-
raise ImproperlyConfigured("SAML_USER_MODEL refers to model '%s' "
46-
"that has not been installed" % model_path)
47-
return user_model
33+
except LookupError:
34+
raise ImproperlyConfigured("SAML_USER_MODEL refers to model '%s' that has not been installed" % model_path)
35+
except ValueError:
36+
raise ImproperlyConfigured("SAML_USER_MODEL must be of the form 'app_label.model_name'")
4837

4938

5039
def get_saml_user_model():
51-
try:
52-
# djangosaml2 custom user model
40+
if hasattr(settings, 'SAML_USER_MODEL'):
5341
return get_model(settings.SAML_USER_MODEL)
54-
except AttributeError:
55-
try:
56-
# Django 1.5 Custom user model
57-
return auth.get_user_model()
58-
except AttributeError:
59-
return auth.models.User
42+
return auth.get_user_model()
6043

6144

6245
class Saml2Backend(ModelBackend):
@@ -164,19 +147,21 @@ def _get_or_create_saml2_user(self, main_attribute, attributes, attribute_mappin
164147
main_attribute)
165148
django_user_main_attribute = self.get_django_user_main_attribute()
166149
user_query_args = self.get_user_query_args(main_attribute)
167-
user_create_defaults = {django_user_main_attribute: main_attribute}
168150

169151
User = get_saml_user_model()
152+
built = False
170153
try:
171-
user, created = User.objects.get_or_create(
172-
defaults=user_create_defaults, **user_query_args)
154+
user = User.objects.get(**user_query_args)
155+
except User.DoesNotExist:
156+
user = User(**{django_user_main_attribute: main_attribute})
157+
built = True
173158
except MultipleObjectsReturned:
174159
logger.error("There are more than one user with %s = %s",
175160
django_user_main_attribute, main_attribute)
176161
return None
177162

178-
if created:
179-
logger.debug('New user created')
163+
if built:
164+
logger.debug('Configuring new user "%s"', main_attribute)
180165
user = self.configure_user(user, attributes, attribute_mapping)
181166
else:
182167
logger.debug('User updated')

djangosaml2/cache.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def __init__(self, django_session, key_suffix):
2525
self.session = django_session
2626
self.key = self.key_prefix + key_suffix
2727

28-
super(DjangoSessionCacheAdapter, self).__init__(self._get_objects())
28+
super().__init__(self._get_objects())
2929

3030
def _get_objects(self):
3131
return self.session.get(self.key, {})
@@ -37,9 +37,9 @@ def sync(self):
3737
# Changes in inner objects do not cause session invalidation
3838
# https://docs.djangoproject.com/en/1.9/topics/http/sessions/#when-sessions-are-saved
3939

40-
#add objects to session
40+
# add objects to session
4141
self._set_objects(dict(self))
42-
#invalidate session
42+
# invalidate session
4343
self.session.modified = True
4444

4545

@@ -49,8 +49,7 @@ class OutstandingQueriesCache(object):
4949
"""
5050

5151
def __init__(self, django_session):
52-
self._db = DjangoSessionCacheAdapter(django_session,
53-
'_outstanding_queries')
52+
self._db = DjangoSessionCacheAdapter(django_session, '_outstanding_queries')
5453

5554
def outstanding_queries(self):
5655
return self._db._get_objects()
@@ -86,4 +85,4 @@ class StateCache(DjangoSessionCacheAdapter):
8685
"""
8786

8887
def __init__(self, django_session):
89-
super(StateCache, self).__init__(django_session, '_state')
88+
super().__init__(django_session, '_state')

0 commit comments

Comments
 (0)