Skip to content

Commit 879bc36

Browse files
authored
Merge branch 'master' into SLO_absence_workaround
2 parents 9aec43e + b670a2b commit 879bc36

24 files changed

+254
-255
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: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,35 @@
1+
dist: bionic
12
language: python
23

34
sudo: false
45

56
matrix:
67
include:
7-
- python: 2.7
8-
env: TOX_ENV=py27-django18
9-
- python: 3.4
10-
env: TOX_ENV=py34-django18
11-
- python: 2.7
12-
env: TOX_ENV=py27-django19
13-
- python: 3.4
14-
env: TOX_ENV=py34-django19
158
- python: 3.5
16-
env: TOX_ENV=py35-django19
17-
- python: 2.7
18-
env: TOX_ENV=py27-django110
19-
- python: 3.4
20-
env: TOX_ENV=py34-django110
21-
- python: 3.5
22-
env: TOX_ENV=py35-django110
23-
- python: 2.7
24-
env: TOX_ENV=py27-django111
25-
- python: 3.4
26-
env: TOX_ENV=py34-django111
27-
- python: 3.5
28-
env: TOX_ENV=py35-django111
9+
env: TOX_ENV=py35-django22
2910
- python: 3.6
30-
env: TOX_ENV=py36-django111
31-
- python: 3.5
32-
env: TOX_ENV=py35-djangomaster
11+
env: TOX_ENV=py36-django22
12+
- python: 3.7
13+
env: TOX_ENV=py37-django22
14+
- python: 3.8
15+
env: TOX_ENV=py38-django22
16+
- python: 3.6
17+
env: TOX_ENV=py36-django30
18+
- python: 3.7
19+
env: TOX_ENV=py37-django30
20+
- python: 3.8
21+
env: TOX_ENV=py38-django30
3322
- python: 3.6
3423
env: TOX_ENV=py36-djangomaster
24+
- python: 3.7
25+
env: TOX_ENV=py37-djangomaster
26+
- python: 3.8
27+
env: TOX_ENV=py38-djangomaster
3528
fast_finish: true
3629
allow_failures:
37-
- env: TOX_ENV=py35-djangomaster
3830
- env: TOX_ENV=py36-djangomaster
31+
- env: TOX_ENV=py37-djangomaster
32+
- env: TOX_ENV=py38-djangomaster
3933

4034
addons:
4135
apt:

CHANGES

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
Changes
22
=======
3+
0.18.1 (2020-02-15)
4+
----------
5+
- Fixed regression from 0.18.0. Thanks to OskarPersson
6+
7+
0.18.0 (2020-02-14)
8+
----------
9+
- Django 3.0 support. Thanks to OskarPersson
10+
- forceauthn and allowcreate support. Thanks to peppelinux
11+
- Dropped support for Python 3.4
12+
- Also thanks to WebSpider, mhindery, DylannCordel, habi3000 for various fixes and improvements
13+
14+
Thanks to plumdog
315

416
0.17.2 (2018-08-29)
517
----------

README.rst

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,13 @@ We will see a typical configuration for protecting a Django project::
192192
saml2.BINDING_HTTP_POST),
193193
],
194194
},
195-
195+
# Mandates that the identity provider MUST authenticate the
196+
# presenter directly rather than rely on a previous security context.
197+
'force_authn': False,
198+
199+
# Enable AllowCreate in NameIDPolicy.
200+
'name_id_format_allow_create': False,
201+
196202
# attributes that this project need to identify a user
197203
'required_attributes': ['uid'],
198204

@@ -309,6 +315,24 @@ setting::
309315
SAML_CONFIG_LOADER = 'python.path.to.your.callable'
310316

311317

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+
312336
User attributes
313337
---------------
314338

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: 18 additions & 29 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):
@@ -89,7 +72,9 @@ def authenticate(self, request, session_info=None, attribute_mapping=None,
8972
else:
9073
logger.error('The nameid is not available. Cannot find user without a nameid.')
9174
else:
92-
saml_user = self.get_attribute_value(django_user_main_attribute, attributes, attribute_mapping)
75+
saml_user = self.get_attribute_value(django_user_main_attribute,
76+
attributes,
77+
attribute_mapping)
9378

9479
if saml_user is None:
9580
logger.error('Could not find saml_user value')
@@ -111,7 +96,11 @@ def get_attribute_value(self, django_field, attributes, attribute_mapping):
11196
logger.debug('attribute_mapping: %s', attribute_mapping)
11297
for saml_attr, django_fields in attribute_mapping.items():
11398
if django_field in django_fields and saml_attr in attributes:
114-
saml_user = attributes[saml_attr][0]
99+
saml_user = attributes.get(saml_attr, [None])[0]
100+
if not saml_user:
101+
logger.error('attributes[saml_attr] attribute '
102+
'value is missing. Probably the user '
103+
'session is expired.')
115104
return saml_user
116105

117106
def is_authorized(self, attributes, attribute_mapping):

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')

djangosaml2/conf.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818

1919
from django.conf import settings
2020
from django.core.exceptions import ImproperlyConfigured
21-
2221
from saml2.config import SPConfig
2322

24-
from djangosaml2.utils import get_custom_setting
23+
from .utils import get_custom_setting
2524

2625

2726
def get_config_loader(path, request=None):

djangosaml2/exceptions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
class IdPConfigurationMissing(Exception):
2+
pass

djangosaml2/models.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

djangosaml2/overrides.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ def do_logout(self, *args, **kwargs):
2121
except AttributeError:
2222
logger.warning('SAML_LOGOUT_REQUEST_PREFERRED_BINDING setting is'
2323
' not defined. Default binding will be used.')
24-
return super(Saml2Client, self).do_logout(*args, **kwargs)
24+
return super().do_logout(*args, **kwargs)

djangosaml2/signals.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,5 @@
1414

1515
import django.dispatch
1616

17-
18-
pre_user_save = django.dispatch.Signal(providing_args=['attributes',
19-
'user_modified'])
17+
pre_user_save = django.dispatch.Signal(providing_args=['attributes', 'user_modified'])
2018
post_authenticated = django.dispatch.Signal(providing_args=['session_info'])

djangosaml2/templates/djangosaml2/example_post_binding_form.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
</p>
1010
<form method="post" action="{{ target_url }}" name="SSO_Login">
1111
{% for key, value in params.items %}
12-
<input type="hidden" name="{{ key|safe }}" value="{{ value|safe }}" />
12+
<input type="hidden" name="{{ key }}" value="{{ value }}" />
1313
{% endfor %}
1414
<input type="submit" value="Log in" />
1515
</form>

0 commit comments

Comments
 (0)