Minimal Intro:
- SSO: Single Sign On
- SLO: Single Log Out
- SP: Service Provider (your web app)
- IdP: Identity Provider (server with some dreadful software like Shibboleth)
- Metadata/Meta: an XML that describes SP or IdP (or some other entity)
- SAML/SAML2: It's just another XML-based enterprise-grade standard that will make you cry blood and shit bricks
Necessary Stuff
- Binary dependencies:
sudo apt install libxml2-dev libxslt1-dev xmlsec1 libxmlsec1-dev pkg-config
- Python dependencies: see
requirements.txt
orsetup.py
- Add the app into
INSTALLED_APPS
- Include the app's
urls.py
into the projecturls.py
urlpatterns
, preferably without a prefix
Development Setup
- Simply use
localhost:8000/dev/
(might change depending on your urlconf) for all the logging-in-out-users-groups stuff - To make your life easier, add the following to your project settings:
LOGIN_URL = urls.reverse_lazy("sso-dev")
- If you want to debug
ssoauth
or need a fully functional SSO during development for some other reason, an example is below. For additional info see production setup chapter andssoauth.app_settings.defaults
. If you also want a working SLO during development you will need SSL for your localhost,nginx
will be your best friend.
""" settings/dev.py """
import os, socket
from django import urls
IDP_META_URL = "https://idp-test.it.hs-hannover.de/idp/shibboleth"
IDP_LOGOUT_URL = "https://idp-test.it.hs-hannover.de/idp/profile/Logout"
SP_KEY = "{project_settings}/cert/sp.key"
SP_CERT = "{project_settings}/cert/sp.pem"
SP_HOST = "localhost"
SP_PORT = 8000
SP_SSL = False
SP_FORCE_ENTITY_ID = "dev-id-{0}-{1}".format(socket.gethostname(), os.path.dirname(os.path.dirname(__file__))) # too many localhosts around
LOGIN_URL = urls.reverse_lazy("sso-dev") # it's "sso-login" for prod
Overriding Log In Pages of Other Apps
There are some apps like django.contrib.admin
or wagtail
that will simply ignore LOGIN_URL
and use their own log in page. If this behavior is undesirable and you would prefer using ssoauth
instead, add the following into your urls.py
(before including URLs of that other app):
re_path(r"^(?:\w+/)?login/?$", ssoauth_views.LogInView.as_view(already_authenticated_403=True)),
- Adjust the path if required
- Optional argument
already_authenticated_403=True
is used to avoid redirect loops (e.g. caused bydjango.contrib.admin
). You can also usealready_authenticated_redirect="url-name"
.
Regarding Logging Out
After logging out locally, user will be redirected to one of the following (with this priority):
-
?next=
GET value (Note: instead ofnext
use usedjango.contrib.auth.REDIRECT_FIELD_NAME
) -
LOGOUT_REDIRECT_URL
(vanilla django setting), recommended values are:-
LOGOUT_REDIRECT_URL = None
for the default behavior -
LOGOUT_REDIRECT_URL = urls.reverse_lazy("sso-logout-idp")
if you want to instantly initiate IDP log out, possibly triggering SLO LOGOUT_REDIRECT_URL = urls.reverse_lazy("sso-logged-out-locally")
-
- Default: redirect to the view that asks users what to do next. Don't forget to override template
ssoauth/logged_out_locally.html
SLO: SLO/SLS is disabled by default. If you want to try your luck with you should add to your project settings SP_SLS_ENABLED = True
Currently only IdP-initiated SLO is supported by this app. The only supported binding type is HTTP-Redirect due to the limitations of the underlying library used.
For SLO with HTTP-Redirect to work, the SLS page must be included as <iframe>
. Your server and/or browser might restrict such behavior. Start with setting SP_SLS_X_FRAME_OPTIONS
(check ssoauth.app_settings.defaults
).
If you have nginx
serving pages to users, you might need to configure x-frame-options
for the SLS view (Only the SLS view, nowhere else!). Additionally you might need to configure CSP on the web server on the IdP side. Anyways it will most likely be a lot of fun for you.
Groups and Permissions
To receive groups over SSO you need a group mapping (and of course a properly configured IdP). You can manage group mapping with group_mapping
management command:
group_mapping add myproject_superusers "CN=MyProjectSuperusers,OU=Foo,OU=Bar,DC=fh-h,DC=de"
To generate a working mapping for hshinfo
groups, use ssoauth_group_mapping
management command in syncds
(you can find one on the sync
server).
Groups are not mapped automatically. The reason is that automatic mapping can pose security risks. Imagine auto-mapping that expects group with name "Superusers"; an intruder could create new group with this name under any path they own and/or create an alias/reference and receive superuser permissions in your project.
Production Settings
This example might be incomplete. See ssoauth.app_settings.defaults
for additional info
""" settings/prod.py """
from django import urls
SP_HOST = "foobar.it.hs-hannover.de" # FQDN of your SP
IDP_META_URL = "https://idp.hs-hannover.de/idp/shibboleth" # production
IDP_LOGOUT_URL = "https://idp.it.hs-hannover.de/idp/profile/Logout" # web page for IdP logout (might initiate SLO)
LOGIN_URL = urls.reverse_lazy("sso-login") # django setting
Certs
- if you don't need your cert to be signed you can use
openssl req -new -x509 -days 3650 -nodes -out sp.pem -keyout sp.key
- create
cert
directory:- inside of project
settings
directory if it's a package - next to project
settings.py
file if it's a module
- inside of project
-
./cert/sp.key
put the private key here -
./cert/sp.pem
put the certificate here, signing is optional - if you prefer your keys somewhere else, set
SP_KEY
andSP_CERT
settings
Add an SP to an IdP
- Ask somebody who knows more. Seriously.
- Try on the test IdP before you brick the production!
- Grab your meta
- Run your project.
- Find meta of your SP (relative path
/saml2/meta
) - Use Ctrl+U ("view source") to get the actual XML, otherwise your browser could mess it up
- IdP:
-
SSH
to the IdP and locate the Shibboleth directory, most likely/opt/shibboleth-idp/
- put your meta into a new file in
./metadata/
and give it a nice verbose name - edit
./conf/metadata-providers.xml
- create a new
<MetadataProvider .../>
element, your best guess is to copy an existing line that belongs to some existing Django project -
id
should be unique -
metadataFile
should point on your new metadata
- create a new
- edit
./conf/attribute-filter.xml
- add a new
<Rule .../>
element inside of<AttributeFilterPolicy id="releaseToDjango">
-
value
(looks like URI) should be theentityID
of your SP (you find it in your meta)
- add a new
-
systemctl restart tomcat8
(you now have some time to grab you a coffee) - ensure the IdP works after restarting!
-