Skip to content

Commit

Permalink
v1.3.0 (#291)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
peppelinux authored Jun 4, 2021
1 parent 8c7a5e9 commit 7ea562f
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 29 deletions.
57 changes: 47 additions & 10 deletions djangosaml2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
UnsolicitedResponse)
from saml2.s_utils import UnsupportedBinding
from saml2.saml import SCM_BEARER
from saml2.saml import AuthnContextClassRef
from saml2.samlp import RequestedAuthnContext
from saml2.samlp import AuthnRequest, IDPEntry, IDPList, Scoping
from saml2.sigver import MissingKey
from saml2.validate import ResponseLifetimeExceed, ToEarly
Expand Down Expand Up @@ -133,6 +135,41 @@ def unknown_idp(self, request, idp):
msg.format('Please contact technical support.'), status=403
)

def load_sso_kwargs_scoping(self, sso_kwargs):
""" Performs IdP Scoping if scoping param is present. """
idp_scoping_param = self.request.GET.get('scoping', None)
if idp_scoping_param:
idp_scoping = Scoping()
idp_scoping.idp_list = IDPList()
idp_scoping.idp_list.idp_entry.append(
IDPEntry(provider_id = idp_scoping_param)
)
sso_kwargs['scoping'] = idp_scoping

def load_sso_kwargs_authn_context(self, sso_kwargs):
# this would work when https://github.com/IdentityPython/pysaml2/pull/807
ac = getattr(self.conf, '_sp_requested_authn_context', {})

# this works even without https://github.com/IdentityPython/pysaml2/pull/807
# hopefully to be removed soon !
if not ac:
scs = getattr(
settings, 'SAML_CONFIG', {}
).get('service', {}).get('sp', {})
ac = scs.get('requested_authn_context', {})
# end transitional things to be removed soon !

if ac:
sso_kwargs["requested_authn_context"] = RequestedAuthnContext(
authn_context_class_ref=[
AuthnContextClassRef(ac['authn_context_class_ref']),
],
comparison = ac.get('comparison', "minimum"),
)

def load_sso_kwargs(self, sso_kwargs):
""" Inherit me if you want to put your desidered things in sso_kwargs """

def get(self, request, *args, **kwargs):
logger.debug('Login process started')
next_path = self.get_next_path(request)
Expand Down Expand Up @@ -166,6 +203,7 @@ def get(self, request, *args, **kwargs):
configured_idps = available_idps(conf)
selected_idp = request.GET.get('idp', None)

self.conf = conf
sso_kwargs = {}

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

# perform IdP Scoping if scoping param is present
idp_scoping_param = request.GET.get('scoping', None)
if idp_scoping_param:
idp_scoping = Scoping()
idp_scoping.idp_list = IDPList()
idp_scoping.idp_list.idp_entry.append(
IDPEntry(provider_id = idp_scoping_param)
)
sso_kwargs['scoping'] = idp_scoping

# choose a binding to try first
binding = getattr(settings, 'SAML_DEFAULT_BINDING',
saml2.BINDING_HTTP_POST)
Expand Down Expand Up @@ -267,6 +295,15 @@ def get(self, request, *args, **kwargs):
# custom nsprefixes
sso_kwargs['nsprefix'] = get_namespace_prefixes()


# Enrich sso_kwargs ...
# idp scoping
self.load_sso_kwargs_scoping(sso_kwargs)
# authn context
self.load_sso_kwargs_authn_context(sso_kwargs)
# other customization to be inherited
self.load_sso_kwargs(sso_kwargs)

logger.debug(f'Redirecting user to the IdP via {binding} binding.')
_msg = 'Unable to know which IdP to use'
http_response = None
Expand Down
17 changes: 8 additions & 9 deletions docs/source/contents/developer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,23 @@ a link to do a global logout.
Unit tests
==========

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

example::

pip install -r requirements-dev.txt
# or
pip install djangosaml2[test]
python3 tests/manage.py migrate

then::

python tests/run_tests.py

or::

cd tests/
then::
cd tests
./manage.py migrate
./manage.py test djangosaml2


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

.. _`tox`: http://pypi.python.org/pypi/tox
Expand Down
19 changes: 11 additions & 8 deletions docs/source/contents/setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ installed apps::
'djangosaml2', # new application
)

.. Note::

When you finish the configuration you can run the djangosaml2 test suite as
you run any other Django application test suite. Just type ``python manage.py
test djangosaml2``.

Python users need to ``pip install djangosaml2[test]`` in order to run the
tests.

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


Authn Context
=============

We can define the authentication context in settings.SAML_CONFIG['service']['sp'] as follows::

'requested_authn_context': {
'authn_context_class_ref': saml2.saml.AUTHN_PASSWORD_PROTECTED,
'comparison': "exact"
}


Custom and dynamic configuration loading
========================================

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def read(*rnames):

setup(
name='djangosaml2',
version='1.2.2',
version='1.3.0',
description='pysaml2 integration for Django',
long_description=read('README.md'),
long_description_content_type='text/markdown',
Expand Down
2 changes: 1 addition & 1 deletion tests/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'NAME': os.path.join(BASE_DIR, 'tests/db.sqlite3'),
}
}

Expand Down

0 comments on commit 7ea562f

Please sign in to comment.