Based on the Django Simple SSO project (https://github.com/ojii/django-simple-sso). MIT license.
Include this app as a dependency in setup.py:
install_requires = [
...
'lizard-auth-client',
...
],
And add it to INSTALLED_APPS
in your django settings:
INSTALLED_APPS = (
...
'lizard_auth_client',
...)
Add the proper URLS to your urls.py. Because the app needs to override the login/logout URLS, import them in the root of your urlpatterns:
urlpatterns = patterns(
'',
(r'', include('lizard_auth_client.urls')),
)
If you use django >= 2.0 you have to use the path(route, view, kwargs=None, name=None)
method like so:
from django.urls import include, path
urlpatterns = patterns(
'',
path(r'', include('lizard_auth_client.urls')),
...
)
Optionally, add our authentication backend in addition to Django's default one:
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend',
'lizard_auth_client.backends.SSOBackend']
Normally, the authentication backend is not needed if you only log in through the reguler part of your website, using the regular django login url. The backend is used if you log in directly via /admin/
or via django rest framework.
Configure the SSO settings as seen in testsettings.py
:
# SSO *can* be disabled for development with local accounts.
SSO_ENABLED = True
# Create a portal on the SSO server, this generates the SSO_KEY and
# SSO_SECRET for you.
# A key identifying this client. Can be published.
SSO_KEY = 'random_generated_key_to_identify_the_client'
# A *secret* shared between client and server.
# Used to sign the messages exchanged between them.
SSO_SECRET = 'random_generated_secret_key_to_sign_exchanged_messages'
# URL used to redirect the user to the SSO server.
# Note: needs a trailing slash
SSO_SERVER_PUBLIC_URL = 'https://external-address.site.tld/'
# URL used for server-to-server communication
# Note: needs a trailing slash
SSO_SERVER_PRIVATE_URL = 'http://10.0.0.1:80/'
The easiest way is to go to the SSO, create a portal in the admin and copy/paste the settings directly from the portal's edit page in the admin. There's a read-only field "settings for the V2 API" there. The result will be something like this:
SSO_ENABLED = True
SSO_USE_V2_login = True
SSO_SERVER_API_START_URL = 'https://sso.lizard.net/api2/'
SSO_KEY = 'sdfkljlkasdflkasfdlkasfdlk;asdflkjlaksdfjlkas'
SSO_SECRET = 'jklsdfjlksdfjklasdfkljasdfjlkasjkd;fasdf'
SSO_ALLOW_ONLY_KNOWN_USERS = True
Note that with these settings, logging in won't be allowed right away due to the SSO_ALLOW_ONLY_KNOWN_USERS
setting, see the section below.
In the V1 API, access to sites is handled in the SSO. In the V2 API, this is considered authorization and thus it is not handled. Which means everyone can theoretically log in to any site.
To prevent this, SSO_ALLOW_ONLY_KNOWN_USERS
is set to True
by default. Only people that already have a local user object are allowed to log in.
To create and manage user objects locally, a view (/sso/user_overview/
) exists that shows the known users, enabled and disabled ones. You can disable/enable users and there's a link to search users on the SSO by email and a link to create a completely new one. Note: you can set SSO_INVITATION_LANGUAGE
, this is the language used in the invitation email send by the SSO to the new user.
For these management views, you need the auth.manage_users
permission. This way you can allow customers without admin acces to manage their users anyhow.
The layout is very, very basic. Create a custom templates/lizard_auth_server/base.html
in your project and make sure there's a {% block content %}
, this is where the actual template content is placed. {{ view.title }}
is available for the <title>
tag.
You'll want to add a link to the lizard_auth_client.user_overview
URL somewhere in your site.
In a Django context, simple configure the app as above, and do:
from lizard_auth_client import client as auth_client
try:
user_data = auth_client.sso_authenticate_django('username', 'password')
except auth_client.AutheticationFailed as ex:
return some_error_handler('Auth failed')
except auth_client.CommunicationError as ex:
return some_error_handler('Temporary comm error')
except:
return some_error_handler('Other error')
It should be usable without Django settings as well:
user_data = auth_client.sso_authenticate('http://url.tld', 'key', 'secret' 'username', 'password')
Since SSO V2, authorisation management has been removed from the SSO server. To still be able to manage user permissions per organisation, management pages have been introduced to lizard-auth-client
. The main page is accessible via /management/organisations/
. Users that are either superusers or have management permissions see a list of manageable organisations on that page. From there on, they can add users to their organisation(s) and manage their permissions.
Users can be added to organisations without assigning permissions to them. This is achieved by storing a UserOrganisationRole
instance that has a connected role. This happens automatically when a user is added to an organisation. The connected role is only used for connecting users to organisations.
Permissions can be added simply by adding a Role
instance. This role will show up automatically as a new role/permission, unless the role code is added to the SSO_IGNORE_ROLE_CODES
list setting.
The management pages depend on django-crispy-forms
. Therefore, to access the role/permission management pages, you need to have django-crispy-forms
installed and have it in your project's INSTALLED_APPS
setting. Also, you need to add the CRISPY_TEMPLATE_PACK
setting to your project:
CRISPY_TEMPLATE_PACK = 'bootstrap3'
Other settings:
SSO_ROLES_LABEL
- the form label of the roles section (default: _("Permissions"))SSO_MANAGER_ROLE_CODES
- role codes that define a manager role (default: ['manager', 'superman', 'manage'])
Lizard-auth-client has two middleware classes.
The first middleware forces a login. If the user is already logged in to the SSO, they are automatically logged in on our site. If not, they are forced to login on the SSO first.
To enable it, add this to your settings' MIDDLEWARE_CLASSES
:
...
'lizard_auth_client.middleware.LoginRequiredMiddleware',
...
The second middleware only attempts a login, it doesn't force it. If the user is already logged in to the SSO, they are automatically logged in on our site. If not, they are not forced to log in on the SSO and simply remain anonymous.
This can be very handy if you point from one site to another and would prefer the user to be logged in, but want to allow anonymous access, too.
To enable it, add this to your settings' MIDDLEWARE_CLASSES
:
...
'lizard_auth_client.middleware.AttemptAutoLoginMiddleware',
...
Note: django.contrib.auth.middleware.AuthenticationMiddleware
, enabled by default, should be above our middleware classes.
The first middleware's behaviour can be achieved by Django's standard @login_required
decorator.
For the second middleware's behaviour we have our own @attempt_auto_login
decorator:
from lizard_auth_client.decorators import attempt_auto_login
(Re)create & activate a virtualenv:
$ python3 -m virtualenv .
Install package and run tests:
$ bin/pip install django==2.2
$ bin/pip install -e .[test]
$ bin/pytest
To not conflict with an optional local lizard-auth-server (running on port 5000, normally), we run on port 5050:
$ bin/python manage.py runserver 5050
For a test in your browser, you'll need to also start a local lizard-auth-server. Or test against the staging SSO. For the V2 API, you can use any of the development portals, as the new V2 API sends through full URLS for the requests coming back to your development laptop, it won't look at the portal's configuration regarding "redirect url" and "allowed domains". So any portal is good, actually. Add the key and secret to lizard_auth_client/local_testsettings.py
:
SSO_KEY = 'kljsdfljkdsfjlkdsf'
SSO_SECRET = 'dfjkladjklsjklsdflkjf'
For local testing of this very app do you need this additional setting:
SSO_STANDALONE = True
This setting is already there in the testsettings.py
.
Go to the lizard_auth_client
subdirectory:
$ docker-compose run web /bin/bash
$ cd lizard_auth_client
$ ../bin/django makemessages --all
Update the translations (for Dutch), for instance with "poedit". Then compile the new translations:
$ ../bin/django compilemessages