Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for signed Authentication Requests #130

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
869ef09
Update README with the changes to be made.
ckagger Nov 7, 2019
59dbff5
Include certificate information in SAML configuration.
agger-magenta Nov 8, 2019
e782e86
Merge pull request #1 from magenta-aps/feature/32731_sp_certificates
agger-magenta Nov 8, 2019
6f7dd47
Move instantiation of cert file etc.
agger-magenta Nov 8, 2019
1919dbc
Change version number.
agger-magenta Sep 28, 2020
6e9b2b5
Version = 2.2.2
agger-magenta Sep 28, 2020
99abd00
Exposed accepted_time_diff parameter (available in pysaml2)
davidmgvaz Oct 20, 2020
f7beebd
use BINDING_HTTP_POST for signon
syre Nov 19, 2020
985ec58
Merge pull request #2 from syre/feature/39731_add_signin_post
agger-magenta Nov 19, 2020
571afd9
Bump version.
agger-magenta Nov 19, 2020
dac7ee0
use next_url if set explicit for JWT redirect
syre Nov 23, 2020
b3d2a15
Merge pull request #3 from syre/feature/39750_add_redirect_if_using_jwt
agger-magenta Nov 24, 2020
d1449d7
Version 2.2.4
agger-magenta Nov 26, 2020
cd9891a
allow parameters in jwt redirect
syre Dec 4, 2020
3a86e5f
add comment
syre Dec 10, 2020
8b81dc8
Merge pull request #4 from syre/allow_parameters_in_redirect
agger-magenta Dec 10, 2020
82a8bbc
Version 2.2.5.
agger-magenta Dec 10, 2020
21acbed
use RelayState for redirect if available
syre Jan 4, 2021
4b826e8
Merge pull request #5 from syre/use-relaystate-when-available-for-red…
agger-magenta Jan 5, 2021
217893a
Version 2.2.6
agger-magenta Jan 5, 2021
75313f9
Merge branch 'master' into master
dals83 Apr 22, 2021
fd01683
Merge pull request #6 from dals83/master
agger-magenta Apr 23, 2021
31de7a9
Version 2.3.0
agger-magenta Apr 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,12 @@ How to use?
'ASSERTION_URL': 'https://mysite.com', # Custom URL to validate incoming SAML requests against
'ENTITY_ID': 'https://mysite.com/saml2_auth/acs/', # Populates the Issuer element in authn request
'NAME_ID_FORMAT': FormatString, # Sets the Format property of authn NameIDPolicy element
'ACCEPTED_TIME_DIFF': 0 # sets the accepted_time_diff
'USE_JWT': False, # Set this to True if you are running a Single Page Application (SPA) with Django Rest Framework (DRF), and are using JWT authentication to authorize client users
'FRONTEND_URL': 'https://myfrontendclient.com', # Redirect URL for the client if you are using JWT auth with DRF. See explanation below
'CERT_FILE': '' # Public part of the service private/public key pair. Must be a PEM formatted certificate chain file.
'KEY_FILE': '' # The name of a PEM formatted file that contains the private key of the service. This is presently used both to encrypt/sign assertions and as the client key in an HTTPS session.
'AUTHN_REQUESTS_SIGNED': False # Indicates if the Authentication Requests sent by this SP should be signed by default.
}

#. In your SAML2 SSO identity provider, set the Single-sign-on URL and Audience
Expand Down Expand Up @@ -207,6 +211,8 @@ behind a reverse proxy.
**NAME_ID_FORMAT** Set to the string 'None', to exclude sending the 'Format' property of the 'NameIDPolicy' element in authn requests.
Default value if not specified is 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'.

**ACCEPTED_TIME_DIFF** Sets the accepted time diff in seconds `PySaml2 Accepted Time Diff <https://pysaml2.readthedocs.io/en/latest/howto/config.html#accepted-time-diff>`_

**USE_JWT** Set this to the boolean True if you are using Django Rest Framework with JWT authentication

**FRONTEND_URL** If USE_JWT is True, you should set the URL of where your frontend is located (will default to DEFAULT_NEXT_URL if you fail to do so). Once the client is authenticated through the SAML/SSO, your client is redirected to the FRONTEND_URL with the user id (uid) and JWT token (token) as query parameters.
Expand Down
57 changes: 46 additions & 11 deletions django_saml2_auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from django.shortcuts import render
from django.views.decorators.csrf import csrf_exempt
from django.template import TemplateDoesNotExist
from django.http import HttpResponseRedirect
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.http import is_safe_url

from rest_auth.utils import jwt_encode
Expand Down Expand Up @@ -93,6 +93,8 @@ def _get_saml_client(domain):
acs_url = domain + get_reverse([acs, 'acs', 'django_saml2_auth:acs'])
metadata = _get_metadata()

authn_requests_signed = settings.SAML2_AUTH.get('AUTHN_REQUESTS_SIGNED', False)

saml_settings = {
'metadata': metadata,
'service': {
Expand All @@ -104,7 +106,7 @@ def _get_saml_client(domain):
],
},
'allow_unsolicited': True,
'authn_requests_signed': False,
'authn_requests_signed': authn_requests_signed,
'logout_requests_signed': True,
'want_assertions_signed': True,
'want_response_signed': False,
Expand All @@ -118,6 +120,15 @@ def _get_saml_client(domain):
if 'NAME_ID_FORMAT' in settings.SAML2_AUTH:
saml_settings['service']['sp']['name_id_format'] = settings.SAML2_AUTH['NAME_ID_FORMAT']

if 'ACCEPTED_TIME_DIFF' in settings.SAML2_AUTH:
saml_settings['accepted_time_diff'] = settings.SAML2_AUTH['ACCEPTED_TIME_DIFF']

if settings.SAML2_AUTH.get('CERT_FILE'):
saml_settings['cert_file'] = settings.SAML2_AUTH['CERT_FILE']

if settings.SAML2_AUTH.get('KEY_FILE'):
saml_settings['key_file'] = settings.SAML2_AUTH['KEY_FILE']

spConfig = Saml2Config()
spConfig.load(saml_settings)
spConfig.allow_unknown_attributes = True
Expand Down Expand Up @@ -155,9 +166,18 @@ def _create_new_user(username, email, firstname, lastname):

@csrf_exempt
def acs(r):
try:
import urlparse as _urlparse
from urllib import unquote
except:
import urllib.parse as _urlparse
from urllib.parse import unquote

saml_client = _get_saml_client(get_current_domain(r))
resp = r.POST.get('SAMLResponse', None)
next_url = r.session.get('login_next_url', _default_next_url())
# Use RelayState if available, else fall back to next_url.
next_url = r.POST.get('RelayState', next_url)

if not resp:
return HttpResponseRedirect(get_reverse([denied, 'denied', 'django_saml2_auth:denied']))
Expand Down Expand Up @@ -204,12 +224,22 @@ def acs(r):
if settings.SAML2_AUTH.get('USE_JWT') is True:
# We use JWT auth send token to frontend
jwt_token = jwt_encode(target_user)
query = '?uid={}&token={}'.format(target_user.id, jwt_token)
params = {"uid": target_user.id, "token": jwt_token}

frontend_url = settings.SAML2_AUTH.get(
'FRONTEND_URL', next_url)

return HttpResponseRedirect(frontend_url+query)
if next_url and next_url != _default_next_url():
frontend_url = next_url

# Reconstruct URL with added parameters.
url_parts = list(_urlparse.urlparse(frontend_url, allow_fragments=False))
query = dict(_urlparse.parse_qsl(url_parts[4]))
query.update(params)

url_parts[4] = _urlparse.urlencode(query)

return HttpResponseRedirect(_urlparse.urlunparse(url_parts))

if is_new_user:
try:
Expand Down Expand Up @@ -247,16 +277,21 @@ def signin(r):
r.session['login_next_url'] = next_url

saml_client = _get_saml_client(get_current_domain(r))
_, info = saml_client.prepare_for_authenticate()
_, info = saml_client.prepare_for_authenticate(binding=BINDING_HTTP_POST, relay_state=next_url)

if info["method"] == "GET":
redirect_url = None

redirect_url = None
for key, value in info['headers']:
if key == 'Location':
redirect_url = value
break

for key, value in info['headers']:
if key == 'Location':
redirect_url = value
break
return HttpResponseRedirect(redirect_url)

return HttpResponseRedirect(redirect_url)
elif info["method"] == "POST":
response_content = info["data"]
return HttpResponse(response_content)


def signout(r):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
setup(
name='django_saml2_auth',

version='2.2.1',
version='2.3.0',

description='Django SAML2 Authentication Made Easy. Easily integrate with SAML2 SSO identity providers like Okta',
long_description=long_description,
Expand Down