Skip to content

Commit 7ea562f

Browse files
authored
v1.3.0 (#291)
* SAML2 Request improved * feat: sso_kwargs now handled with some custom methods ... that can be inherited :) * feat: authn context support, with or without this IdentityPython/pysaml2#807 (better with!) * feat: authn context documentation * fix: Documentation for developers, unit tests * v1.3.0
1 parent 8c7a5e9 commit 7ea562f

File tree

5 files changed

+68
-29
lines changed

5 files changed

+68
-29
lines changed

djangosaml2/views.py

+47-10
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
UnsolicitedResponse)
4444
from saml2.s_utils import UnsupportedBinding
4545
from saml2.saml import SCM_BEARER
46+
from saml2.saml import AuthnContextClassRef
47+
from saml2.samlp import RequestedAuthnContext
4648
from saml2.samlp import AuthnRequest, IDPEntry, IDPList, Scoping
4749
from saml2.sigver import MissingKey
4850
from saml2.validate import ResponseLifetimeExceed, ToEarly
@@ -133,6 +135,41 @@ def unknown_idp(self, request, idp):
133135
msg.format('Please contact technical support.'), status=403
134136
)
135137

138+
def load_sso_kwargs_scoping(self, sso_kwargs):
139+
""" Performs IdP Scoping if scoping param is present. """
140+
idp_scoping_param = self.request.GET.get('scoping', None)
141+
if idp_scoping_param:
142+
idp_scoping = Scoping()
143+
idp_scoping.idp_list = IDPList()
144+
idp_scoping.idp_list.idp_entry.append(
145+
IDPEntry(provider_id = idp_scoping_param)
146+
)
147+
sso_kwargs['scoping'] = idp_scoping
148+
149+
def load_sso_kwargs_authn_context(self, sso_kwargs):
150+
# this would work when https://github.com/IdentityPython/pysaml2/pull/807
151+
ac = getattr(self.conf, '_sp_requested_authn_context', {})
152+
153+
# this works even without https://github.com/IdentityPython/pysaml2/pull/807
154+
# hopefully to be removed soon !
155+
if not ac:
156+
scs = getattr(
157+
settings, 'SAML_CONFIG', {}
158+
).get('service', {}).get('sp', {})
159+
ac = scs.get('requested_authn_context', {})
160+
# end transitional things to be removed soon !
161+
162+
if ac:
163+
sso_kwargs["requested_authn_context"] = RequestedAuthnContext(
164+
authn_context_class_ref=[
165+
AuthnContextClassRef(ac['authn_context_class_ref']),
166+
],
167+
comparison = ac.get('comparison', "minimum"),
168+
)
169+
170+
def load_sso_kwargs(self, sso_kwargs):
171+
""" Inherit me if you want to put your desidered things in sso_kwargs """
172+
136173
def get(self, request, *args, **kwargs):
137174
logger.debug('Login process started')
138175
next_path = self.get_next_path(request)
@@ -166,6 +203,7 @@ def get(self, request, *args, **kwargs):
166203
configured_idps = available_idps(conf)
167204
selected_idp = request.GET.get('idp', None)
168205

206+
self.conf = conf
169207
sso_kwargs = {}
170208

171209
# Do we have a Discovery Service?
@@ -200,16 +238,6 @@ def get(self, request, *args, **kwargs):
200238
if selected_idp is None:
201239
selected_idp = list(configured_idps.keys())[0]
202240

203-
# perform IdP Scoping if scoping param is present
204-
idp_scoping_param = request.GET.get('scoping', None)
205-
if idp_scoping_param:
206-
idp_scoping = Scoping()
207-
idp_scoping.idp_list = IDPList()
208-
idp_scoping.idp_list.idp_entry.append(
209-
IDPEntry(provider_id = idp_scoping_param)
210-
)
211-
sso_kwargs['scoping'] = idp_scoping
212-
213241
# choose a binding to try first
214242
binding = getattr(settings, 'SAML_DEFAULT_BINDING',
215243
saml2.BINDING_HTTP_POST)
@@ -267,6 +295,15 @@ def get(self, request, *args, **kwargs):
267295
# custom nsprefixes
268296
sso_kwargs['nsprefix'] = get_namespace_prefixes()
269297

298+
299+
# Enrich sso_kwargs ...
300+
# idp scoping
301+
self.load_sso_kwargs_scoping(sso_kwargs)
302+
# authn context
303+
self.load_sso_kwargs_authn_context(sso_kwargs)
304+
# other customization to be inherited
305+
self.load_sso_kwargs(sso_kwargs)
306+
270307
logger.debug(f'Redirecting user to the IdP via {binding} binding.')
271308
_msg = 'Unable to know which IdP to use'
272309
http_response = None

docs/source/contents/developer.rst

+8-9
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,23 @@ a link to do a global logout.
2121
Unit tests
2222
==========
2323

24-
You can also run the unit tests as follows::
24+
Djangosaml2 have a legacy way to do tests, using an example project in `tests` directory.
25+
This means that to run tests you have to clone the repository, then install djangosaml2, then run tests using the example project.
26+
27+
example::
2528

2629
pip install -r requirements-dev.txt
2730
# or
2831
pip install djangosaml2[test]
29-
python3 tests/manage.py migrate
30-
31-
then::
3232

33-
python tests/run_tests.py
3433

35-
or::
36-
37-
cd tests/
34+
then::
35+
cd tests
36+
./manage.py migrate
3837
./manage.py test djangosaml2
3938

4039

41-
If you have `tox`_ installed you can simply call tox inside the root directory
40+
If you have `tox`_ installed you can simply call `tox` inside the root directory
4241
and it will run the tests in multiple versions of Python.
4342

4443
.. _`tox`: http://pypi.python.org/pypi/tox

docs/source/contents/setup.rst

+11-8
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,6 @@ installed apps::
4848
'djangosaml2', # new application
4949
)
5050

51-
.. Note::
52-
53-
When you finish the configuration you can run the djangosaml2 test suite as
54-
you run any other Django application test suite. Just type ``python manage.py
55-
test djangosaml2``.
56-
57-
Python users need to ``pip install djangosaml2[test]`` in order to run the
58-
tests.
5951

6052
SameSite cookie
6153
===============
@@ -221,6 +213,17 @@ This parameter can be combined with the IdP parameter if multiple IdPs are prese
221213
Currently there is support for a single IDPEntry in the IDPList.
222214

223215

216+
Authn Context
217+
=============
218+
219+
We can define the authentication context in settings.SAML_CONFIG['service']['sp'] as follows::
220+
221+
'requested_authn_context': {
222+
'authn_context_class_ref': saml2.saml.AUTHN_PASSWORD_PROTECTED,
223+
'comparison': "exact"
224+
}
225+
226+
224227
Custom and dynamic configuration loading
225228
========================================
226229

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def read(*rnames):
2424

2525
setup(
2626
name='djangosaml2',
27-
version='1.2.2',
27+
version='1.3.0',
2828
description='pysaml2 integration for Django',
2929
long_description=read('README.md'),
3030
long_description_content_type='text/markdown',

tests/settings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@
7575
DATABASES = {
7676
'default': {
7777
'ENGINE': 'django.db.backends.sqlite3',
78-
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
78+
'NAME': os.path.join(BASE_DIR, 'tests/db.sqlite3'),
7979
}
8080
}
8181

0 commit comments

Comments
 (0)