Skip to content

Commit

Permalink
Documentation updates for new release
Browse files Browse the repository at this point in the history
  • Loading branch information
mhindery committed Jan 11, 2019
1 parent ba324d7 commit d566992
Show file tree
Hide file tree
Showing 18 changed files with 113 additions and 54 deletions.
32 changes: 32 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Contributing to the djangosaml2idp package

First of all, thank you for contributing to the project! The more, the merrier.

I welcome all contributions; issues/bug reports, adding tests, adding or requesting functionality, improving documentation, suggesting implementation improvements ...

## About the project

This package started off on a need-to-have basis for use in the company I work for. I did not spend time to build an all-encompassing feature set; so what we did not use and need was not implemented. Several features since then have been added by contributors with their own needs in mind. Therefore, if something is missing that you need, feel free to ask about it and I'm happy to take a look.

While I created this package due to a need in the company for something like this, I maintain it privately. I am not paid for it nor work on it during 'company time', it is a private side project. My response time will therefore be dependent on my available time and is a best-effort matter. Please keep this in mind and my apologies in advance if a response runs a bit late. We all have lives to lead :)

## Practicalities

Some notes for when you want to start adding code to the project.

### Tests

- Unfortunately there are not a lot of formal tests (there's an idea for a contribution ;)) Please make sure that the ones who are present, succeed by running the test suite (it uses `pytest`, instructions are in the [README.rst](README.rst))

### Codestyle

- Formatting: I mostly like to follow the PEP8 standard, with some customized rules applied.
- For code quality and consistency checks, I recommend the [PyLama](https://github.com/klen/pylama) tool. You run it by executing
```bash
pylama --options pytest.ini
```
in the root of the project. It only outputs violations, so if you don't get any output, you did great. Please ensure pylama succeeds when creating a PR.
- When you create a PR for functionality or bugfix, don't mix in unrelated formatting changes along with it.


The `pytest.ini` file contains sections for both PEP8 and pylama settings, which should be used for this project.
45 changes: 30 additions & 15 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ djangosaml2idp


djangosaml2idp implements the Identity Provider side of the SAML2 protocol for Django.
It builds on top of `PySAML2 <https://github.com/IdentityPython/pysaml2>`_, and is used in production.
It builds on top of `PySAML2 <https://github.com/IdentityPython/pysaml2>`_, and is production-ready.

Package version 0.3.3 is the last Python 2 / Django 1.8-1.11 compatible release. Versions starting from 0.4.0 are for Python 3 and Django 2.x.
Package version 0.3.3 was the last Python 2 / Django 1.8-1.11 compatible release. Versions starting from 0.4.0 are for Python 3 and Django 2.x.

Any contributions, feature requests, proposals, ideas ... are welcome!
Any contributions, feature requests, proposals, ideas ... are welcome! See the `CONTRIBUTING <https://github.com/OTA-Insight/djangosaml2idp/blob/master/CHANGELOG.md>`_ for some tips.

Installation
============
Expand All @@ -47,17 +47,6 @@ Now you can install the djangosaml2idp package using pip. This will also install
pip install djangosaml2idp


Running the test suite
======================
Install the dev dependencies in ``requirements-dev.txt``::

pip install -r requirements-dev.txt

Run ``py.test`` from the project root::

py.test


Configuration & Usage
=====================

Expand Down Expand Up @@ -150,6 +139,18 @@ You also have to define a mapping for each SP you talk to::
That's all for the IdP configuration. Assuming you run the Django development server on localhost:8000, you can get its metadata by visiting http://localhost:8000/idp/metadata/.
Use this metadata xml to configure your SP. Place the metadata xml from that SP in the location specified in the config dict (sp_metadata.xml in the example above).

Further optional configuration options
======================================

In the `SAML_IDP_SPCONFIG` you define a `processor` value. This is a hook to customize some authorization checks. By default, the included `BaseProcessor` is used, which allows
every user to login on the IdP. You can customize this behaviour by subclassing the `BaseProcessor` and overriding its `has_access(self, request)` method. This method should return true or false,
depending if the user has permission to log in for the SP / IdP. The processor has the SP entity ID available as `self._entity_id`, and received the request (with an authenticated request.user on it)
as parameter to the `has_access` function. This way, you should have the necessary flexibility to perform whatever checks you need.
An example `processor subclass <https://github.com/OTA-Insight/djangosaml2idp/blob/master/example_setup/idp/idp/processors.py>`_ can be found in the IdP of the included example.

Without custom setting, users will be identified by the `USERNAME_FIELD` property on the user Model you use. By Django defaults this will be the username.
You can customize which field is used for the identifier by adding `SAML_IDP_DJANGO_USERNAME_FIELD` to your settings with as value the attribute to use on your user instance.

Customizing error handling
==========================

Expand Down Expand Up @@ -184,7 +185,21 @@ There are three main components to adding multiple factor support.

3. Update your urls.py and add an override for name='saml_multi_factor' - ensure it comes before importing the djangosaml2idp urls file so your custom view is used instead of the built-in one.


Running the test suite
======================
Install the dev dependencies in ``requirements-dev.txt``::

pip install -r requirements-dev.txt

Run ``py.test`` from the project root::

py.test



Example project
---------------
``example_project`` contains a barebone demo setup to demonstrate the login-logout functionality.
The directory ``example_project`` contains a barebone demo setup to demonstrate the login-logout functionality.
It consists of a Service Provider implemented with `djangosaml2 <https://github.com/knaperek/djangosaml2/>`_ and an Identity Provider using ``djangosaml2idp``.
The readme in that folder contains more information on how to run it.
2 changes: 1 addition & 1 deletion djangosaml2idp/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '0.4.2'
__version__ = '0.5.0'
3 changes: 3 additions & 0 deletions djangosaml2idp/processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def enable_multifactor(self, user):
return False

def get_user_id(self, user):
""" Get identifier for a user. Take the one defined in settings.SAML_IDP_DJANGO_USERNAME_FIELD first, if not set
use the USERNAME_FIELD property which is set on the user Model. This defaults to the user.username field.
"""
user_field = getattr(settings, 'SAML_IDP_DJANGO_USERNAME_FIELD', None) or \
getattr(user, 'USERNAME_FIELD', 'username')
return str(getattr(user, user_field))
Expand Down
5 changes: 2 additions & 3 deletions djangosaml2idp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ def dispatch(self, request, *args, **kwargs):
return super(IdPHandlerViewMixin, self).dispatch(request, *args, **kwargs)

def get_processor(self, entity_id, sp_config):
""" "Instantiate user-specified processor or fallback to all-access base processor
""" Instantiate user-specified processor or default to an all-access base processor.
Raises an exception if the configured processor class can not be found or initialized.
"""
processor_string = sp_config.get('processor', None)
if processor_string:
Expand Down Expand Up @@ -115,7 +116,6 @@ def get(self, request, *args, **kwargs):
req_info = self.IDP.parse_authn_request(request.session['SAMLRequest'], binding)
except Exception as excp:
return self.handle_error(request, exception=excp)
# TODO this is taken from example, but no idea how this works or whats it does. Check SAML2 specification?
# Signed request for HTTP-REDIRECT
if "SigAlg" in request.session and "Signature" in request.session:
_certs = self.IDP.metadata.certs(req_info.message.issuer.text, "any", "signing")
Expand Down Expand Up @@ -148,7 +148,6 @@ def get(self, request, *args, **kwargs):

identity = self.get_identity(processor, request.user, sp_config)

# TODO investigate how this works, because I don't get it. Specification?
req_authn_context = req_info.message.requested_authn_context or PASSWORD
AUTHN_BROKER = AuthnBroker()
AUTHN_BROKER.add(authn_context_class_ref(req_authn_context), "")
Expand Down
13 changes: 13 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,16 @@ You also have to define a mapping for each SP you talk to::

That's all for the IdP configuration. Assuming you run the Django development server on localhost:8000, you can get its metadata by visiting http://localhost:8000/idp/metadata/.
Use this metadata xml to configure your SP. Place the metadata xml from that SP in the location specified in the config dict (sp_metadata.xml in the example above).


Further optional configuration options
======================================

In the `SAML_IDP_SPCONFIG` you define a `processor` value. This is a hook to customize some authorization checks. By default, the included `BaseProcessor` is used, which allows
every user to login on the IdP. You can customize this behaviour by subclassing the `BaseProcessor` and overriding its `has_access(self, request)` method. This method should return true or false,
depending if the user has permission to log in for the SP / IdP. The processor has the SP entity ID available as `self._entity_id`, and received the request (with an authenticated request.user on it)
as parameter to the `has_access` function. This way, you should have the necessary flexibility to perform whatever checks you need.
An example `processor subclass <https://github.com/OTA-Insight/djangosaml2idp/blob/master/example_setup/idp/idp/processors.py>`_ can be found in the IdP of the included example.

Without custom setting, users will be identified by the `USERNAME_FIELD` property on the user Model you use. By Django defaults this will be the username.
You can customize which field is used for the identifier by adding `SAML_IDP_DJANGO_USERNAME_FIELD` to your settings with as value the attribute to use on your user instance.
6 changes: 3 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ djangosaml2idp


djangosaml2idp implements the Identity Provider side of the SAML2 protocol for Django.
It builds on top of `PySAML2 <https://github.com/IdentityPython/pysaml2>`_, and is used in production.
It builds on top of `PySAML2 <https://github.com/IdentityPython/pysaml2>`_, and is production-ready.

Package version 0.3.3 is the last Python 2 / Django 1.8-1.11 compatible release. Versions starting from 0.4.0 are for Python 3 and Django 2.x.
Package version 0.3.3 was the last Python 2 / Django 1.8-1.11 compatible release. Versions starting from 0.4.0 are for Python 3 and Django 2.x.

Any contributions, feature requests, proposals, ideas ... are welcome!
Any contributions, feature requests, proposals, ideas ... are welcome! See the `CONTRIBUTING <https://github.com/OTA-Insight/djangosaml2idp/blob/master/CHANGELOG.md>`_ for some tips.


Table of contents
Expand Down
5 changes: 2 additions & 3 deletions example_setup/idp/idp/processors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from django.contrib.auth.models import Group

from djangosaml2idp.processors import BaseProcessor


Expand All @@ -12,5 +10,6 @@ class GroupProcessor(BaseProcessor):
"""
group = "ExampleGroup"

def has_access(self, user):
def has_access(self, request):
user = request.user
return user.is_superuser or user.is_staff or user.groups.filter(name=self.group).exists()
18 changes: 9 additions & 9 deletions example_setup/idp/idp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,16 @@

STATIC_URL = '/static/'

STATICFILES_DIRS = (
BASE_DIR + '/static/',
STATICFILES_DIRS = (
BASE_DIR + '/static/',
)

### Everything above are default settings made by django-admin startproject
### The following is added for djangosaml2idp IdP configuration.
# Everything above are default settings made by django-admin startproject
# The following is added for djangosaml2idp IdP configuration.

import saml2
from saml2.saml import NAMEID_FORMAT_EMAILADDRESS, NAMEID_FORMAT_UNSPECIFIED
from saml2.sigver import get_xmlsec_binary
import saml2 # noqa
from saml2.saml import NAMEID_FORMAT_EMAILADDRESS, NAMEID_FORMAT_UNSPECIFIED # noqa
from saml2.sigver import get_xmlsec_binary # noqa

LOGIN_URL = '/login/'
LOGOUT_URL = '/logout/'
Expand All @@ -139,7 +139,7 @@
BASE_URL = 'http://localhost:9000/idp'

SAML_IDP_CONFIG = {
'debug' : DEBUG,
'debug': DEBUG,
'xmlsec_binary': get_xmlsec_binary(['/opt/local/bin', '/usr/bin/xmlsec1']),
'entityid': '%s/metadata' % BASE_URL,
'description': 'Example IdP setup',
Expand Down Expand Up @@ -186,4 +186,4 @@
'is_superuser': 'is_superuser',
}
}
}
}
2 changes: 1 addition & 1 deletion example_setup/idp/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
django>=2.0
djangosaml2idp==0.4.1
djangosaml2idp==0.5.0
pysaml2>=4.5.0
2 changes: 1 addition & 1 deletion example_setup/sp/sp/saml2_config/attribute-maps/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,4 +322,4 @@
'x121Address': 'urn:mace:dir:attribute-def:x121Address',
'x500UniqueIdentifier': 'urn:mace:dir:attribute-def:x500UniqueIdentifier',
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,4 @@
'labeledURI': UMICH+'57',
'uid': UCL_DIR_PILOT+'1'
}
}
}
4 changes: 2 additions & 2 deletions example_setup/sp/sp/saml2_config/attribute-maps/shibboleth.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
EDUPERSON_OID+'3': 'eduPersonOrgDN',
NOREDUPERSON_OID+'3': 'norEduPersonBirthDate',
},
"to":{
"to": {
'roleOccupant': X500ATTR+'33',
'gn': X500ATTR+'42',
'norEduPersonNIN': NOREDUPERSON_OID+'5',
Expand Down Expand Up @@ -187,4 +187,4 @@
'sn': X500ATTR+'4',
'domainComponent': UCL_DIR_PILOT+'25',
}
}
}
12 changes: 6 additions & 6 deletions example_setup/sp/sp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@

STATIC_URL = '/static/'

### Everything above are default settings made by django-admin startproject
### The following is added for djangosaml2 SP configuration.
### See their docs for explanation of all options.
# Everything above are default settings made by django-admin startproject
# The following is added for djangosaml2 SP configuration.
# See their docs for explanation of all options.

import saml2
from saml2.saml import NAMEID_FORMAT_EMAILADDRESS
from saml2.sigver import get_xmlsec_binary
import saml2 # noqa
from saml2.saml import NAMEID_FORMAT_EMAILADDRESS # noqa
from saml2.sigver import get_xmlsec_binary # noqa

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
Expand Down
4 changes: 2 additions & 2 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
django>2.0,<2.1
pysaml2>=4.4.0
django>2.0,<2.2
pysaml2>=4.5.0

pytest==4.0.1
pytest-django==3.4.4
Expand Down
6 changes: 4 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,14 @@
zip_safe=False,
include_package_data=True,
classifiers=[
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
'Environment :: Web Environment',
"Framework :: Django",
"Framework :: Django :: 2.0",
"Framework :: Django :: 2.1",
'Intended Audience :: Developers',
"Framework :: Django :: 2.2",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: Apache Software License",
'Operating System :: OS Independent',
'Programming Language :: Python',
Expand Down
4 changes: 0 additions & 4 deletions tests/test_processor.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import pytest

from django.contrib.auth import get_user_model

from djangosaml2idp.processors import BaseProcessor


User = get_user_model()


Expand Down
2 changes: 1 addition & 1 deletion tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class CustomProcessor(BaseProcessor):

class TestIdPHandlerViewMixin:
def test_get_identity_provides_extra_config(self):
obj = IdPHandlerViewMixin()
IdPHandlerViewMixin()

def test_get_processor_errors_if_processor_cannot_be_loaded(self):
sp_config = {
Expand Down

0 comments on commit d566992

Please sign in to comment.