forked from fangli/django-saml2-auth
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
fang.li
committed
Apr 12, 2016
1 parent
6121c21
commit 9c446ce
Showing
11 changed files
with
442 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
This project is written and maintained by Fang Li and | ||
various contributors: | ||
|
||
|
||
Django Saml2 Auth | ||
----------------- | ||
|
||
- Fang Li | ||
|
||
|
||
|
||
Pysaml2 | ||
------- | ||
|
||
- Roland Hedberg and it's contributors | ||
|
||
|
||
|
||
Contributors | ||
------------ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright 2016 Fang Li | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
include LICENSE README.rst AUTHORS.rst | ||
recursive-include django_saml2_auth/templates/django_saml2_auth *.html | ||
global-exclude *.pyc[co] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
===================================== | ||
Django SAML2 Authentication Made Easy | ||
===================================== | ||
|
||
:Author: Fang Li | ||
:Version: 1.0.1b1 | ||
|
||
.. image:: https://api.travis-ci.org/fangli/django_saml2_auth.png?branch=master | ||
:target: https://travis-ci.org/fangli/django_saml2_auth | ||
|
||
.. image:: https://img.shields.io/pypi/v/django_saml2_auth.svg | ||
:target: https://pypi.python.org/pypi/django_saml2_auth | ||
|
||
.. image:: https://img.shields.io/pypi/dm/django_saml2_auth.svg | ||
:target: https://pypi.python.org/pypi/django_saml2_auth | ||
|
||
This project aim to provide a dead simple way to integrate your Django powered app with SAML2 Authentication. | ||
Try it now, and get rid of the complicated configuration of saml. | ||
|
||
Any SAML2 based SSO(Single-Sign-On) with dynamic metadata configuration was supported by this django plugin, Such as okta. | ||
|
||
|
||
|
||
Install | ||
======= | ||
|
||
You can install this plugin via `pip`: | ||
|
||
.. code-block:: bash | ||
# pip install django_saml2_auth | ||
or from source: | ||
|
||
.. code-block:: bash | ||
# git clone https://github.com/fangli/django-saml2-auth | ||
# cd django-saml2-auth | ||
# python setup.py install | ||
What does this plugin do? | ||
========================= | ||
|
||
This plugin takes over django's login page and redirect user to SAML2 SSO authentication service. While a user | ||
logged in and redirected back, it will check if this user is already in system. If not, it will create the user using django's default UserModel, | ||
otherwise redirect the user to the last visited page. | ||
|
||
|
||
|
||
How to use? | ||
=========== | ||
|
||
1. Override the default login page in root urls.py, by adding these lines **BEFORE** any `urlpatterns`: | ||
|
||
.. code-block:: python | ||
# This is the SAML2 related URLs, you can change "^saml2_auth/" to any path you want, like "^sso_auth/", "^sso_login/", etc. (required) | ||
url(r'^saml2_auth/', include('django_saml2_auth.urls')), | ||
# If you want to replace the default user login with SAML2, just use the following line (optional) | ||
url(r'^accounts/login/$', 'django_saml2_auth.views.signin'), | ||
# If you want to replace the admin login with SAML2, use the following line (optional) | ||
url(r'^admin/login/$', 'django_saml2_auth.views.signin'), | ||
2. In settings.py, add SAML2 related configuration. | ||
|
||
Please note only METADATA_AUTO_CONF_URL is required. The following block just shows the full featured configuration and their default values. | ||
|
||
.. code-block:: python | ||
SAML2_AUTH = { | ||
'METADATA_AUTO_CONF_URL': '[The auto(dynamic) metadata configuration URL of SAML2]', | ||
'NEW_USER_PROFILE': { | ||
'USER_GROUPS': [], # The default group name when a new user logged in | ||
'ACTIVE_STATUS': True, # The default active status of new user | ||
'STAFF_STATUS': True, # The staff status of new user | ||
'SUPERUSER_STATUS': False, # The superuser status of new user | ||
}, | ||
'ATTRIBUTES_MAP': { # Change Email/UserName/FirstName/LastName to corresponding SAML2 userprofile attributes. | ||
'email': 'Email', | ||
'username': 'UserName', | ||
'first_name': 'FirstName', | ||
'last_name': 'LastName', | ||
} | ||
} | ||
3. Well done. | ||
|
||
|
||
|
||
Customize | ||
========= | ||
|
||
You are allowed to override the default permission `denied` page and new user `welcome` page. | ||
|
||
Just put a template named 'django_saml2_auth/welcome.html' or 'django_saml2_auth/denied.html' under your project's template folder. | ||
|
||
In case of 'django_saml2_auth/welcome.html' existed, when a new user logged in, we'll show this template instead of redirecting user to the | ||
previous visited page. So you can have some first-visit notes and welcome words in this page. You can get user context in the template by | ||
using `user` context. | ||
|
||
By the way, we have a built-in logout page as well, if you want to use it, just add the following lines into your urls.py, before any | ||
`urlpatterns`: | ||
|
||
.. code-block:: python | ||
# If you want to replace the default user logout with plugin built-in page, just use the following line (optional) | ||
url(r'^accounts/logout/$', 'django_saml2_auth.views.signout'), | ||
# If you want to replace the admin logout with SAML2, use the following line (optional) | ||
url(r'^admin/logout/$', 'django_saml2_auth.views.signout'), | ||
In a similar way, you can customize this logout template by added a template 'django_saml2_auth/signout.html'. | ||
|
||
|
||
By default, we assume your SAML2 service provided user attribute Email/UserName/FirstName/LastName. Please change it to the correct | ||
user attributes mapping. | ||
|
||
|
||
|
||
How to Contribute | ||
================= | ||
|
||
#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. | ||
#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it). | ||
#. Write a test which shows that the bug was fixed or that the feature works as expected. | ||
#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_. | ||
|
||
.. _`the repository`: http://github.com/fangli/django-saml2-auth | ||
.. _AUTHORS: https://github.com/fangli/django-saml2-auth/blob/master/AUTHORS.rst |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = '1.0.1b1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{% load i18n %} | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>{% trans "Permission Denied" %}</title> | ||
</head> | ||
<body> | ||
<h2>{% trans "Sorry, you are not allowed to access this app" %}</h2> | ||
<hr> | ||
<p>{% trans "If you think it's an incorrect configuration, write email to your system administrator" %}</p> | ||
</body> | ||
</html> |
13 changes: 13 additions & 0 deletions
13
django_saml2_auth/templates/django_saml2_auth/signout.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{% load i18n %} | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta charset="utf-8"> | ||
<title>{% trans "Signed out" %}</title> | ||
</head> | ||
<body> | ||
<h2>{% trans "You have signed out successfully." %}</h2> | ||
<hr> | ||
<p>{% trans "If you want to login again or switch to another account, please do it in SSO." %}</p> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from django.conf.urls import url, patterns | ||
|
||
|
||
app_name = 'django_saml2_auth' | ||
|
||
urlpatterns = patterns( | ||
'django_saml2_auth.views', | ||
url(r'^acs/$', "acs", name="acs"), | ||
url(r'^welcome/$', "welcome", name="welcome"), | ||
url(r'^denied/$', "denied", name="denied"), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
#!/usr/bin/env python | ||
# -*- coding:utf-8 -*- | ||
|
||
|
||
import urllib2 | ||
from saml2 import ( | ||
BINDING_HTTP_POST, | ||
BINDING_HTTP_REDIRECT, | ||
entity, | ||
) | ||
from saml2.client import Saml2Client | ||
from saml2.config import Config as Saml2Config | ||
|
||
from django.conf import settings | ||
from django.core.urlresolvers import reverse | ||
from django.contrib.auth.models import (User, Group) | ||
from django.contrib.auth.decorators import login_required | ||
from django.contrib.auth import login, logout | ||
from django.shortcuts import render | ||
from django.views.decorators.csrf import csrf_exempt | ||
from django.template import TemplateDoesNotExist | ||
from django.http import HttpResponseRedirect | ||
|
||
|
||
def get_current_domain(r): | ||
return '{scheme}://{host}'.format( | ||
scheme=r.scheme, | ||
host=r.get_host(), | ||
) | ||
|
||
|
||
def _get_saml_client(domain): | ||
acs_url = domain + reverse('acs') | ||
import tempfile | ||
tmp = tempfile.NamedTemporaryFile() | ||
f = open(tmp.name, 'w') | ||
f.write(urllib2.urlopen(settings.SAML2_AUTH['METADATA_AUTO_CONF_URL']).read()) | ||
f.close() | ||
saml_settings = { | ||
'metadata': { | ||
"local": [tmp.name], | ||
}, | ||
'service': { | ||
'sp': { | ||
'endpoints': { | ||
'assertion_consumer_service': [ | ||
(acs_url, BINDING_HTTP_REDIRECT), | ||
(acs_url, BINDING_HTTP_POST) | ||
], | ||
}, | ||
'allow_unsolicited': True, | ||
'authn_requests_signed': False, | ||
'logout_requests_signed': True, | ||
'want_assertions_signed': True, | ||
'want_response_signed': False, | ||
}, | ||
}, | ||
} | ||
|
||
spConfig = Saml2Config() | ||
spConfig.load(saml_settings) | ||
spConfig.allow_unknown_attributes = True | ||
saml_client = Saml2Client(config=spConfig) | ||
tmp.close() | ||
return saml_client | ||
|
||
|
||
@login_required | ||
def welcome(r): | ||
try: | ||
return render(r, 'django_saml2_auth/welcome.html', context={'user': r.user}) | ||
except TemplateDoesNotExist: | ||
return HttpResponseRedirect(reverse('admin:index')) | ||
|
||
|
||
def denied(r): | ||
return render(r, 'django_saml2_auth/denied.html') | ||
|
||
|
||
def _create_new_user(username, email, firstname, lastname): | ||
user = User.objects.create_user(username, email) | ||
user.first_name = firstname | ||
user.last_name = lastname | ||
user.groups = [Group.objects.get(name=x) for x in settings.SAML2_AUTH.get('NEW_USER_PROFILE', {}).get('USER_GROUPS', [])] | ||
user.is_active = settings.SAML2_AUTH.get('NEW_USER_PROFILE', {}).get('ACTIVE_STATUS', True) | ||
user.is_staff = settings.SAML2_AUTH.get('NEW_USER_PROFILE', {}).get('STAFF_STATUS', True) | ||
user.is_superuser = settings.SAML2_AUTH.get('NEW_USER_PROFILE', {}).get('SUPERUSER_STATUS', False) | ||
user.save() | ||
return user | ||
|
||
|
||
@csrf_exempt | ||
def acs(r): | ||
saml_client = _get_saml_client(get_current_domain(r)) | ||
resp = r.POST.get('SAMLResponse', None) | ||
next_url = r.session.get('login_next_url', reverse('admin:index')) | ||
|
||
if not resp: | ||
return HttpResponseRedirect(reverse('denied')) | ||
|
||
authn_response = saml_client.parse_authn_request_response( | ||
resp, entity.BINDING_HTTP_POST) | ||
if authn_response is None: | ||
return HttpResponseRedirect(reverse('denied')) | ||
|
||
user_identity = authn_response.get_identity() | ||
if user_identity is None: | ||
return HttpResponseRedirect(reverse('denied')) | ||
|
||
user_email = user_identity[settings.SAML2_AUTH.get('ATTRIBUTES_MAP', {}).get('email', 'Email')][0] | ||
user_name = user_identity[settings.SAML2_AUTH.get('ATTRIBUTES_MAP', {}).get('username', 'UserName')][0] | ||
user_first_name = user_identity[settings.SAML2_AUTH.get('ATTRIBUTES_MAP', {}).get('first_name', 'FirstName')][0] | ||
user_last_name = user_identity[settings.SAML2_AUTH.get('ATTRIBUTES_MAP', {}).get('last_name', 'LastName')][0] | ||
|
||
target_user = None | ||
is_new_user = False | ||
|
||
try: | ||
target_user = User.objects.get(username=user_name) | ||
except User.DoesNotExist: | ||
target_user = _create_new_user(user_name, user_email, user_first_name, user_last_name) | ||
is_new_user = True | ||
|
||
r.session.flush() | ||
|
||
if target_user.is_active: | ||
target_user.backend = 'django.contrib.auth.backends.ModelBackend' | ||
login(r, target_user) | ||
else: | ||
return HttpResponseRedirect(reverse('denied')) | ||
|
||
if is_new_user: | ||
try: | ||
return render(r, 'django_saml2_auth/welcome.html', context={'user': r.user}) | ||
except TemplateDoesNotExist: | ||
return HttpResponseRedirect(next_url) | ||
else: | ||
return HttpResponseRedirect(next_url) | ||
|
||
|
||
def signin(r): | ||
import urlparse | ||
from urllib import unquote | ||
next_url = r.GET.get('next', reverse('admin:index')) | ||
|
||
try: | ||
if "next=" in unquote(next_url): | ||
next_url = urlparse.parse_qs(urlparse.urlparse(unquote(next_url)).query)['next'][0] | ||
except: | ||
next_url = r.GET.get('next', reverse('admin:index')) | ||
|
||
r.session['login_next_url'] = next_url | ||
|
||
saml_client = _get_saml_client(get_current_domain(r)) | ||
_, info = saml_client.prepare_for_authenticate() | ||
|
||
redirect_url = None | ||
|
||
for key, value in info['headers']: | ||
if key == 'Location': | ||
redirect_url = value | ||
break | ||
|
||
return HttpResponseRedirect(redirect_url) | ||
|
||
|
||
def signout(r): | ||
logout(r) | ||
return render(r, 'django_saml2_auth/signout.html') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[bdist_wheel] | ||
universal=1 |
Oops, something went wrong.