first commit
This commit is contained in:
commit
37728a07bc
|
@ -0,0 +1,13 @@
|
|||
MANIFEST
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.db
|
||||
.*.swp
|
||||
cache/
|
||||
dist/
|
||||
static/
|
||||
doc/_build
|
||||
authentic.egg-info
|
||||
local_settings.py
|
||||
log.log
|
||||
authentic2/locale/fr/LC_MESSAGES/django.mo
|
|
@ -0,0 +1,10 @@
|
|||
Install
|
||||
=======
|
||||
|
||||
You just have to install the package in your virtualenv and relaunch, it will
|
||||
be automatically loaded by the plugin framework.
|
||||
|
||||
|
||||
Settings
|
||||
========
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
__version__ = '1.0'
|
||||
|
||||
class Plugin(object):
|
||||
def __init__(self):
|
||||
from authentic2.decorators import TRANSIENT_USER_TYPES
|
||||
from . import transient
|
||||
|
||||
TRANSIENT_USER_TYPES.append(transient.SAML2TransientUser)
|
||||
|
||||
def get_before_urls(self):
|
||||
from . import urls
|
||||
return urls.urlpatterns
|
||||
|
||||
def get_apps(self):
|
||||
return [__name__, 'authentic2.saml']
|
||||
|
||||
def get_authentication_backends(self):
|
||||
return (
|
||||
'authentic2_auth_saml2.backends.AuthSAML2PersistentBackend',
|
||||
'authentic2_auth_saml2.backends.AuthSAML2TransientBackend',
|
||||
)
|
||||
|
||||
def get_auth_frontends(self):
|
||||
return ('authentic2_auth_saml2.frontend.AuthSAML2Frontend',)
|
||||
|
||||
def get_idp_backends(self):
|
||||
return ('authentic2_auth_saml2.backends.AuthSAML2Backend',)
|
||||
|
||||
def logout_list(self, request):
|
||||
return []
|
||||
|
||||
def get_saml2_authn_context(self, backend_cls):
|
||||
import lasso
|
||||
|
||||
if backend_cls.startswith('authentic2_auth_saml2.'):
|
||||
return lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED
|
||||
return None
|
|
@ -0,0 +1,24 @@
|
|||
class AppSettings(object):
|
||||
|
||||
def __init__(self, prefix):
|
||||
self.prefix = prefix
|
||||
|
||||
@property
|
||||
def AUTOMATIC_GRANT(self):
|
||||
return self._setting('AUTOMATIC_GRANT', ())
|
||||
|
||||
def _setting(self, name, dflt):
|
||||
from django.conf import settings
|
||||
getter = getattr(settings,
|
||||
'ALLAUTH_SETTING_GETTER',
|
||||
lambda name, dflt: getattr(settings, name, dflt))
|
||||
return getter(self.prefix + name, dflt)
|
||||
|
||||
|
||||
|
||||
# Ugly? Guido recommends this himself ...
|
||||
# http://mail.python.org/pipermail/python-ideas/2012-May/014969.html
|
||||
import sys
|
||||
app_settings = AppSettings('A2_OAUTH2_')
|
||||
app_settings.__name__ = __name__
|
||||
sys.modules[__name__] = app_settings
|
|
@ -0,0 +1,151 @@
|
|||
import string
|
||||
import random
|
||||
import logging
|
||||
import lasso
|
||||
import urllib
|
||||
|
||||
from django.db import transaction
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from authentic2.compat import get_user_model
|
||||
from authentic2.saml.common import \
|
||||
lookup_federation_by_name_id_and_provider_id, add_federation, \
|
||||
get_idp_options_policy
|
||||
from authentic2.saml.models import LIBERTY_SESSION_DUMP_KIND_SP, \
|
||||
LibertySessionDump, LibertyProvider
|
||||
|
||||
from .transient import SAML2TransientUser
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthSAML2Backend:
|
||||
def logout_list(self, request):
|
||||
q = LibertySessionDump. \
|
||||
objects.filter(django_session_key=request.session.session_key,
|
||||
kind=LIBERTY_SESSION_DUMP_KIND_SP)
|
||||
if not q:
|
||||
logger.debug('logout_list: no LibertySessionDump found')
|
||||
return []
|
||||
'''
|
||||
We deal with a single IdP session
|
||||
'''
|
||||
try:
|
||||
provider_id = lasso.Session(). \
|
||||
newFromDump(q[0].session_dump.encode('utf-8')). \
|
||||
get_assertions().keys()[0]
|
||||
except:
|
||||
return []
|
||||
if not provider_id:
|
||||
return []
|
||||
logger.debug('logout_list: Found session for %s' % provider_id)
|
||||
name = provider_id
|
||||
provider = None
|
||||
try:
|
||||
provider = LibertyProvider.objects.get(entity_id=provider_id)
|
||||
name = provider.name
|
||||
except LibertyProvider.DoesNotExist:
|
||||
logger.error('logout_list: session found for unknown provider %s' \
|
||||
% provider_id)
|
||||
return []
|
||||
|
||||
policy = get_idp_options_policy(provider)
|
||||
if not policy:
|
||||
logger.error('logout_list: No policy found for %s' % provider_id)
|
||||
return []
|
||||
elif not policy.forward_slo:
|
||||
logger.info('logout_list: %s configured to not reveive slo' \
|
||||
% provider_id)
|
||||
return []
|
||||
else:
|
||||
code = '<div>'
|
||||
code += _('Sending logout to %(pid)s....') % { 'pid': name or provider_id }
|
||||
query = urllib.urlencode({
|
||||
'entity_id': provider_id
|
||||
})
|
||||
url = '%s?%s' % (reverse('a2-auth-saml2-slo'), query)
|
||||
code += '''<iframe src="%s" marginwidth="0" marginheight="0" \
|
||||
scrolling="no" style="border: none" width="16" height="16" onload="window.iframe_count -= 1; console.log(window.location.href + ' decrement iframe_count');"></iframe></div>''' \
|
||||
% url
|
||||
return [ code ]
|
||||
|
||||
|
||||
class AuthSAML2PersistentBackend:
|
||||
supports_object_permissions = False
|
||||
supports_anonymous_user = False
|
||||
|
||||
def authenticate(self, name_id=None, provider_id=None, create=False):
|
||||
'''Authenticate persistent NameID'''
|
||||
if not name_id or not provider_id:# or not name_id.nameQualifier:
|
||||
return None
|
||||
#fed = lookup_federation_by_name_identifier(name_id=name_id)
|
||||
fed = lookup_federation_by_name_id_and_provider_id(name_id, provider_id)
|
||||
if fed is None:
|
||||
if create == True:
|
||||
return self.create_user(name_id=name_id,
|
||||
provider_id=provider_id)
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return fed.user
|
||||
|
||||
def get_user(self, user_id):
|
||||
User = get_user_model()
|
||||
try:
|
||||
return User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
@transaction.commit_on_success
|
||||
def create_user(self, username=None, name_id=None, provider_id=None):
|
||||
'''Create a new user mapping to the given NameID'''
|
||||
if not name_id or \
|
||||
name_id.format != \
|
||||
lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT or \
|
||||
not name_id.nameQualifier:
|
||||
raise ValueError('Invalid NameID')
|
||||
if not username:
|
||||
# FIXME: maybe keep more information in the forged username
|
||||
username = 'saml2-%s' % ''. \
|
||||
join([random.SystemRandom().choice(string.letters) for x in range(10)])
|
||||
User = get_user_model()
|
||||
user = User()
|
||||
user.username = username
|
||||
if hasattr(User, 'set_unusable_password'):
|
||||
user.set_unusable_password()
|
||||
user.is_active = True
|
||||
user.save()
|
||||
logger.info('automatic creation of user %r', user)
|
||||
add_federation(user, name_id=name_id, provider_id=provider_id)
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def get_saml2_authn_context(cls):
|
||||
return lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED
|
||||
|
||||
class AuthSAML2TransientBackend:
|
||||
supports_object_permissions = False
|
||||
supports_anonymous_user = False
|
||||
|
||||
def authenticate(self, name_id=None):
|
||||
'''Create temporary user for transient NameID'''
|
||||
if not name_id or \
|
||||
name_id.format != \
|
||||
lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT or \
|
||||
not name_id.content:
|
||||
return None
|
||||
user = SAML2TransientUser(id=name_id.content)
|
||||
return user
|
||||
|
||||
def get_user(self, user_id):
|
||||
'''Create temporary user for transient NameID'''
|
||||
return SAML2TransientUser(id=user_id)
|
||||
|
||||
@classmethod
|
||||
def get_saml2_authn_context(cls):
|
||||
return lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
from django.conf import settings
|
||||
from django.utils.http import urlencode
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from functools import wraps
|
||||
|
||||
def anonymous_only(func):
|
||||
'''Logout before entering this view'''
|
||||
@wraps(func)
|
||||
def f(request, *args, **kwargs):
|
||||
if request.user.is_authenticated():
|
||||
current_url = request.build_absolute_uri()
|
||||
query = urlencode({REDIRECT_FIELD_NAME: current_url})
|
||||
logout_url = '{0}?{1}'.format(settings.LOGOUT_URL, query)
|
||||
return HttpResponseRedirect(logout_url)
|
||||
return func(request, *args, **kwargs)
|
||||
return f
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
from django import forms
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from authentic2.utils import IterableFactory
|
||||
from authentic2.saml import models
|
||||
|
||||
|
||||
def provider_list_to_choices(qs):
|
||||
for idp in qs:
|
||||
yield idp.entity_id, idp.name
|
||||
|
||||
def get_idp_list():
|
||||
qs = models.LibertyProvider.objects.idp_enabled().order_by('name')
|
||||
return provider_list_to_choices(qs)
|
||||
|
||||
class AuthSAML2Form(forms.Form):
|
||||
entity_id = forms.ChoiceField(label=_('Choose your identity provider'),
|
||||
choices=IterableFactory(get_idp_list))
|
|
@ -0,0 +1,42 @@
|
|||
import urllib
|
||||
|
||||
from django.utils.translation import gettext_noop
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
|
||||
from authentic2.saml.models import LibertyProvider
|
||||
|
||||
from . import forms, views_profile
|
||||
|
||||
class AuthSAML2Frontend(object):
|
||||
def enabled(self):
|
||||
return LibertyProvider.objects.idp_enabled().exists()
|
||||
|
||||
def id(self):
|
||||
return 'saml2'
|
||||
|
||||
def name(self):
|
||||
return gettext_noop('SAML 2.0')
|
||||
|
||||
def form(self):
|
||||
return forms.AuthSAML2Form
|
||||
|
||||
def post(self, request, form, nonce, next):
|
||||
entity_id = form.cleaned_data['entity_id']
|
||||
query = urllib.urlencode({
|
||||
'entity_id': entity_id,
|
||||
REDIRECT_FIELD_NAME: next
|
||||
})
|
||||
return HttpResponseRedirect('/authsaml2/sso?%s' % query)
|
||||
|
||||
def get_context(self):
|
||||
'''Specific context variable used by the specific template'''
|
||||
qs = LibertyProvider.objects.idp_enabled().order_by('name')
|
||||
choices = forms.provider_list_to_choices(qs)
|
||||
return { 'idp_providers': choices }
|
||||
|
||||
def template(self):
|
||||
return 'authsaml2/login_form.html'
|
||||
|
||||
def profile(self, request):
|
||||
return views_profile.profile(request)
|
|
@ -0,0 +1,339 @@
|
|||
# French translation of Authentic
|
||||
# Copyright (C) 2010, 2011 Entr'ouvert
|
||||
# This file is distributed under the same license as the Authentic package.
|
||||
# Frederic Peters <fpeters@entrouvert.com>, 2010.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Authentic\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-02-27 16:42+0100\n"
|
||||
"PO-Revision-Date: 2014-02-27 16:42+0100\n"
|
||||
"Last-Translator: Mikaël Ates <mates@entrouvert.com>\n"
|
||||
"Language-Team: None\n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n>1;\n"
|
||||
|
||||
#: backends.py:64
|
||||
#, python-format
|
||||
msgid "Sending logout to %(pid)s...."
|
||||
msgstr "Envoi de la deconnesion a %(pid)s...."
|
||||
|
||||
#: forms.py:16
|
||||
msgid "Choose your identity provider"
|
||||
msgstr "Choisissez votre fournisseur d'identité"
|
||||
|
||||
#: frontend.py:19
|
||||
msgid "SAML 2.0"
|
||||
msgstr "SAML 2.0"
|
||||
|
||||
#: models.py:91
|
||||
msgid "Anonymous"
|
||||
msgstr "Anonyme"
|
||||
|
||||
#: saml2_endpoints.py:119 saml2_endpoints.py:125
|
||||
msgid "sso: Service provider not configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:131
|
||||
msgid "sso: No SAML2 identity provider selected"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:139
|
||||
msgid "sso: The provider does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:144
|
||||
msgid "sso: Unable to create Login object"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:153
|
||||
#, python-format
|
||||
msgid "sso: %s does not have any supported SingleSignOn endpoint"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:159
|
||||
#, python-format
|
||||
msgid "sso: initAuthnRequest %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:165
|
||||
msgid "sso: No IdP policy defined"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:171
|
||||
#, python-format
|
||||
msgid "SSO: buildAuthnRequestMsg %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:198
|
||||
msgid "singleSignOnArtifact: Service provider not configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:216
|
||||
msgid "singleSignOnArtifact: Unable to create Login object"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:222
|
||||
msgid "singleSignOnArtifact: No message given."
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:243
|
||||
#, python-format
|
||||
msgid "singleSignOnArtifact: provider %r unknown"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:252
|
||||
#, python-format
|
||||
msgid "singleSignOnArtifact: initRequest %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:260
|
||||
#, python-format
|
||||
msgid "singleSignOnArtifact: buildRequestMsg %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:272
|
||||
#, python-format
|
||||
msgid ""
|
||||
"singleSignOnArtifact: Failure to communicate with artifact "
|
||||
"resolver %r"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:277
|
||||
#, python-format
|
||||
msgid ""
|
||||
"singleSignOnArtifact: Artifact resolver at %r returned an empty "
|
||||
"response"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:293
|
||||
#, python-format
|
||||
msgid "singleSignOnArtifact: processResponseMsg raised %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:308
|
||||
msgid "singleSignOnPost: Service provider not configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:314
|
||||
msgid "singleSignOnPost: Unable to create Login object"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:323
|
||||
msgid "singleSignOnPost: No message given."
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:350
|
||||
#, python-format
|
||||
msgid "singleSignOnPost: provider %r unknown"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:361
|
||||
#, python-format
|
||||
msgid "singleSignOnPost: %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:384
|
||||
msgid "sso_after_response: error checking authn response"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:390
|
||||
#, python-format
|
||||
msgid "sso_after_response: acceptSso raised %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:526
|
||||
msgid "sso_after_response: No IdP policy defined"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:575
|
||||
msgid ""
|
||||
"sso_after_response: No backend for temporary federation "
|
||||
"is configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:596
|
||||
msgid ""
|
||||
"sso_after_response: Transient access policy: Configuration error"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:672
|
||||
msgid ""
|
||||
"sso_after_response: You were not asked your consent for "
|
||||
"account linking"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:689
|
||||
msgid ""
|
||||
"sso_after_response: Persistent Account policy: Configuration "
|
||||
"error"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:693
|
||||
msgid ""
|
||||
"sso_after_response: Transient access policy: NameId format not "
|
||||
"supported"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:716
|
||||
msgid "finish_federation: Service provider not configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:722
|
||||
msgid "finish_federation: Unable to create Login object"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:729
|
||||
msgid "finish_federation: Error loading session."
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:745
|
||||
msgid ""
|
||||
"SSO/finish_federation: Error adding new federation for "
|
||||
"this user"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:784
|
||||
msgid "finish_federation: Unable to perform federation"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1250
|
||||
msgid "fedTerm/SP UI: No provider for defederation"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1255
|
||||
msgid "fedTerm/SP UI: Unable to defederate a not logged user!"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1261
|
||||
msgid "fedTerm/SP UI: Service provider not configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1268
|
||||
msgid "fedTerm/SP UI: No such identity provider."
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1278
|
||||
msgid "fedTerm/SP UI: Not a valid federation"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1294
|
||||
#, python-format
|
||||
msgid "fedTerm/SP UI: %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1302 saml2_endpoints.py:1333
|
||||
#, python-format
|
||||
msgid "fedTerm/SP SOAP: %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1311
|
||||
msgid ""
|
||||
"fedTerm/SP SOAP: Unable to perform SOAP defederation "
|
||||
"request"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1320 saml2_endpoints.py:1354
|
||||
#, python-format
|
||||
msgid "fedTerm/SP Redirect: %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1342
|
||||
msgid ""
|
||||
"fedTerm/SP SOAP: Unable to perform SOAP defederation request"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1359
|
||||
msgid "Unknown HTTP method."
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1372
|
||||
msgid "fedTerm/SP Redirect: Service provider not configured"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1380
|
||||
msgid "fedTerm/SP Redirect: Error managing manage dump"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1395
|
||||
msgid "fedTerm/SP Redirect: Defederation failed"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1421
|
||||
#, python-format
|
||||
msgid "fedTerm/Return: provider %r unknown"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1428
|
||||
#, python-format
|
||||
msgid "fedTerm/manage_name_id_return: %s"
|
||||
msgstr ""
|
||||
|
||||
#: saml2_endpoints.py:1476
|
||||
#, python-format
|
||||
msgid "fedTerm/SOAP: provider %r unknown"
|
||||
msgstr ""
|
||||
|
||||
#: utils.py:42
|
||||
#, python-format
|
||||
msgid "An error happened. Report this %s to the administrator."
|
||||
msgstr ""
|
||||
|
||||
#: views_disco.py:53
|
||||
msgid "redirect_to_disco: unable to build disco request"
|
||||
msgstr ""
|
||||
|
||||
#: views_disco.py:63
|
||||
msgid "HTTP request not supported"
|
||||
msgstr ""
|
||||
|
||||
#: views_profile.py:52
|
||||
msgid "Successful federation deletion."
|
||||
msgstr ""
|
||||
|
||||
#: templates/authsaml2/account_linking.html:5
|
||||
msgid "Log in to link your account"
|
||||
msgstr "Connectez-vous pour lier vos comptes"
|
||||
|
||||
#: templates/authsaml2/account_linking.html:9
|
||||
msgid "Log in to link with your existing account"
|
||||
msgstr "Connectez-vous pour lier avec un compte existant"
|
||||
|
||||
#: templates/authsaml2/account_linking.html:17
|
||||
#: templates/authsaml2/account_linking.html:24
|
||||
msgid "Username:"
|
||||
msgstr "Nom d'utilisateur :"
|
||||
|
||||
#: templates/authsaml2/account_linking.html:20
|
||||
#: templates/authsaml2/account_linking.html:28
|
||||
msgid "Password:"
|
||||
msgstr "Mot de passe :"
|
||||
|
||||
#: templates/authsaml2/account_linking.html:32
|
||||
#: templates/authsaml2/login_form.html:6 templates/authsaml2/profile.html:25
|
||||
msgid "Log in"
|
||||
msgstr "S'identifier"
|
||||
|
||||
#: templates/authsaml2/error_authsaml2.html:8
|
||||
msgid "Back"
|
||||
msgstr "Retour"
|
||||
|
||||
#: templates/authsaml2/profile.html:3
|
||||
msgid "SAML2 Federations"
|
||||
msgstr "Fédérations SAML2"
|
||||
|
||||
#: templates/authsaml2/profile.html:9
|
||||
msgid "Delete a federation?"
|
||||
msgstr "Supprimer une fédération ?"
|
||||
|
||||
#: templates/authsaml2/profile.html:13
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: templates/authsaml2/profile.html:21
|
||||
msgid "Add a federation?"
|
||||
msgstr "Ajouter une fédération ?"
|
|
@ -0,0 +1,17 @@
|
|||
from django.dispatch import Signal
|
||||
|
||||
#authz_decision
|
||||
authz_decision = Signal(providing_args = ["request","attributes","provider"])
|
||||
|
||||
#user login
|
||||
auth_login = Signal(providing_args = ["request","attributes"])
|
||||
|
||||
#user logout
|
||||
auth_logout = Signal(providing_args = ["user"])
|
||||
|
||||
from authentic2.saml.common import authz_decision_cb
|
||||
|
||||
authz_decision.connect(authz_decision_cb,
|
||||
dispatch_uid='authz_decision_on_attributes')
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Log in to link your account" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>* {% trans "Log in to link with your existing account" %}</p>
|
||||
<div id="login-actions">
|
||||
<form id="login-form" method="post" action="{% url "a2-auth-saml2-account-linking" %}?request_id={{ request_id}}">
|
||||
{% csrf_token %}
|
||||
<ul class="errorlist">
|
||||
{% for error in form.non_field_errors %}
|
||||
<li>{{ error|escape }}</li>
|
||||
{% endfor %}
|
||||
{% for error in form.username.errors %}
|
||||
<li>{% trans "Username:" %} {{ error|escape }}</li>
|
||||
{% endfor %}
|
||||
{% for error in form.password.errors %}
|
||||
<li>{% trans "Password:" %} {{ error|escape }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p>
|
||||
<label for="id_username">{% trans "Username:" %}</label>
|
||||
<input id="id_username" type="text" name="username" maxlength="30" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_password">{% trans "Password:" %}</label>
|
||||
<input type="password" name="password" id="id_password" />
|
||||
</p>
|
||||
|
||||
<input type="submit" value="{% trans 'Log in' %}" class="submit" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
document.getElementById('id_username').focus();
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block bodyargs %}onload="setTimeout(function () { window.location='{{ next_page }}' }, {{ redir_timeout }})"{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<p><a href="{{ back }}">{% trans "Back" %}<a/></p>
|
||||
{% endblock %}
|
|
@ -0,0 +1,8 @@
|
|||
{% load i18n %}
|
||||
<div>
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="{{ submit_name }}" value="{% trans "Log in" %}"/>
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,30 @@
|
|||
{% load i18n %}
|
||||
{% if form or federations %}
|
||||
<h4>{% trans "SAML2 Federations" %}</h4>
|
||||
|
||||
<div>
|
||||
|
||||
{% if linked_providers %}
|
||||
<p>
|
||||
<h5>{% trans "Delete a federation?" %}</h5>
|
||||
{% for provider_id, name in linked_providers %}
|
||||
<form action="{% url 'a2-auth-saml2-delete-federation' %}/{{ provider_id }}/" method="post">
|
||||
<label for="id_del_fed">{{ name }}</label>
|
||||
<input type="submit" class="submit-link" value="{% trans "Delete" %}">
|
||||
</form>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if form %}
|
||||
<p>
|
||||
<h5>{% trans "Add a federation?" %}</h5>
|
||||
<form method="get" action="{% url "a2-auth-saml2-sso" %}">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" name="submit-authsaml2" value="{% trans "Log in" %}"/>
|
||||
</form>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1 @@
|
|||
{% extends "rest_framework/base.html" %}
|
|
@ -0,0 +1,92 @@
|
|||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.db.models.manager import EmptyManager
|
||||
from django.contrib.auth.models import _user_get_all_permissions, _user_has_perm, _user_has_module_perms
|
||||
|
||||
|
||||
class FakePk:
|
||||
name = 'pk'
|
||||
|
||||
class FakeMeta:
|
||||
pk = FakePk()
|
||||
|
||||
class SAML2TransientUser(object):
|
||||
'''Class compatible with django.contrib.auth.models.User
|
||||
which represent an user authenticated using a Transient
|
||||
federation'''
|
||||
id = None
|
||||
pk = None
|
||||
is_staff = False
|
||||
is_active = False
|
||||
is_superuser = False
|
||||
_groups = EmptyManager()
|
||||
_user_permissions = EmptyManager()
|
||||
_meta = FakeMeta()
|
||||
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
self.pk = id
|
||||
|
||||
def __unicode__(self):
|
||||
return 'AnonymousUser'
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf-8')
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return 1 # instances always return the same hash value
|
||||
|
||||
def save(self, **kwargs):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_password(self, raw_password):
|
||||
raise NotImplementedError
|
||||
|
||||
def check_password(self, raw_password):
|
||||
raise NotImplementedError
|
||||
|
||||
def _get_groups(self):
|
||||
return self._groups
|
||||
groups = property(_get_groups)
|
||||
|
||||
def _get_user_permissions(self):
|
||||
return self._user_permissions
|
||||
user_permissions = property(_get_user_permissions)
|
||||
|
||||
def get_group_permissions(self, obj=None):
|
||||
return set()
|
||||
|
||||
def get_all_permissions(self, obj=None):
|
||||
return _user_get_all_permissions(self, obj=obj)
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
return _user_has_perm(self, perm, obj=obj)
|
||||
|
||||
def has_perms(self, perm_list, obj=None):
|
||||
for perm in perm_list:
|
||||
if not self.has_perm(perm, obj):
|
||||
return False
|
||||
return True
|
||||
|
||||
def has_module_perms(self, module):
|
||||
return _user_has_module_perms(self, module)
|
||||
|
||||
def is_anonymous(self):
|
||||
#XXX: Should return True
|
||||
return False
|
||||
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def get_username(self):
|
||||
return _('Anonymous')
|
||||
username = property(get_username)
|
|
@ -0,0 +1,45 @@
|
|||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('authentic2.authsaml2.views',
|
||||
url(r'^metadata$', 'metadata'),
|
||||
# Receive request from user interface
|
||||
url(r'^sso/$', 'sso', name='a2-auth-saml2-sso'),
|
||||
url(r'^account-linking/(?P<response_id>.*)/$', 'account_linking',
|
||||
name='a2-auth-saml2-account-linking'),
|
||||
url(r'^singleSignOnArtifact$', 'assertion_consumer_artifact'),
|
||||
url(r'^singleSignOnPost$', 'assertion_consumer_post'),
|
||||
# Receive request from functions
|
||||
url(r'^sp_slo/$', 'sp_slo', name='a2-auth-saml2-slo'),
|
||||
# Receive response from Redirect SP initiated
|
||||
url(r'^singleLogoutReturn$', 'slo_sp_response'),
|
||||
# Receive request from SOAP IdP initiated
|
||||
url(r'^singleLogoutSOAP$', 'slo_soap'),
|
||||
# Receive request from Redirect IdP initiated
|
||||
url(r'^singleLogout$', 'singleLogout'),
|
||||
# Back of SLO treatment by the IdP Side
|
||||
url(r'^finish_slo$', 'finish_slo',
|
||||
name='a2-auth-saml2-finish-slo'),
|
||||
# Receive request from user interface
|
||||
url(r'^federationTermination$', 'federationTermination'),
|
||||
# Receive response from Redirect SP initiated
|
||||
url(r'^manageNameIdReturn$', 'manageNameIdReturn'),
|
||||
# Receive request from SOAP IdP initiated
|
||||
url(r'^manageNameIdSOAP$', 'manageNameIdSOAP'),
|
||||
# Receive request from Redirect IdP initiated
|
||||
url(r'^manageNameId$', 'manageNameId'),
|
||||
# Receive request from Redirect IdP initiated
|
||||
#Send idp discovery request
|
||||
)
|
||||
|
||||
urlpatterns += patterns('authentic2.authsaml2.views_disco',
|
||||
url(r'^redirect_to_disco/$', 'redirect_to_disco',
|
||||
name='a2-auth-saml2-redirect-to-disco'),
|
||||
#receive idp discovery response
|
||||
url(r'^discoveryReturn/$', 'disco_response',
|
||||
name='a2-auth-saml2-disco-response'),
|
||||
)
|
||||
|
||||
urlpatterns += patterns('authentic2.authsaml2.views_profile',
|
||||
url(r'^delete_federation/(?P<provider_id>\d+)/$', 'delete_federation',
|
||||
name='a2-auth-saml2-delete-federation'),
|
||||
)
|
|
@ -0,0 +1,148 @@
|
|||
import re
|
||||
import time
|
||||
import logging
|
||||
|
||||
from django.template import RequestContext
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.shortcuts import render_to_response
|
||||
from django.contrib import messages
|
||||
from django.conf import settings
|
||||
|
||||
from authentic2.saml.models import (nameid2kwargs, LibertySession,
|
||||
LibertyFederation)
|
||||
|
||||
__redirection_timeout = 1600
|
||||
|
||||
__root_refererer_re = re.compile('^(https?://[^/]*/?)')
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def error_page(request, message=None, back=None, logger=None,
|
||||
default_message=True, timer=False):
|
||||
'''View that show a simple error page to the user with a back link.
|
||||
|
||||
back - url for the back link, if None, return to root of the referer
|
||||
or the local root.
|
||||
'''
|
||||
if logger:
|
||||
logger.debug('Showing message %r on an error page' % message)
|
||||
else:
|
||||
logging.debug('Showing message %r on an error page' % message)
|
||||
if back is None:
|
||||
referer = request.META.get('HTTP_REFERER')
|
||||
if referer:
|
||||
root_referer = __root_refererer_re.match(referer)
|
||||
if root_referer:
|
||||
back = root_referer.group(1)
|
||||
if back is None:
|
||||
back = '/'
|
||||
global __redirection_timeout
|
||||
context = RequestContext(request)
|
||||
if timer:
|
||||
context['redir_timeout'] = __redirection_timeout
|
||||
context['next_page'] = back
|
||||
display_message = getattr(settings, 'DISPLAY_MESSAGE_ERROR_PAGE', ())
|
||||
if default_message and not display_message:
|
||||
messages.add_message(request, messages.ERROR,
|
||||
_('An error happened. Report this %s to the administrator.') % \
|
||||
time.strftime("[%Y-%m-%d %a %H:%M:%S]", time.localtime()))
|
||||
elif message:
|
||||
messages.add_message(request, messages.ERROR, message)
|
||||
return render_to_response('authsaml2/error_authsaml2.html', {'back': back},
|
||||
context_instance=context)
|
||||
|
||||
# Used to register requested url during SAML redirections
|
||||
def register_next_target(request, url=None):
|
||||
if url:
|
||||
next = url
|
||||
else:
|
||||
next = request.GET.get(REDIRECT_FIELD_NAME)
|
||||
if not next:
|
||||
next = '/'
|
||||
request.session['next'] = next
|
||||
logger.debug('saving next target %r', next)
|
||||
|
||||
def get_registered_url(request):
|
||||
if 'next' in request.session:
|
||||
return request.session['next']
|
||||
return None
|
||||
|
||||
def register_request_id(request, request_id):
|
||||
request.session['saml_request_id'] = request_id
|
||||
|
||||
# Used for account linking
|
||||
def save_federation_temp(request, login, attributes):
|
||||
if login and login.identity:
|
||||
request.session['identity_dump'] = login.identity.dump()
|
||||
request.session['remoteProviderId'] = login.remoteProviderId
|
||||
request.session['nameId'] = login.nameIdentifier
|
||||
request.session['attributes'] = attributes
|
||||
|
||||
def load_federation_temp(request, login):
|
||||
if 'identity_dump' in request.session:
|
||||
login.setIdentityFromDump(request.session['identity_dump'])
|
||||
|
||||
def save_login_session(request, login):
|
||||
try:
|
||||
session_index = login.response.assertion[0].authnStatement[0].sessionIndex
|
||||
except (AttributeError, IndexError):
|
||||
return
|
||||
LibertySession.objects.get_or_create(
|
||||
django_session_key=request.session.session_key,
|
||||
session_index=session_index,
|
||||
provider_id=login.remoteProviderId,
|
||||
**nameid2kwargs(login.nameIdentifier))
|
||||
|
||||
def logout_session(session_key, entity_id):
|
||||
qs = LibertySession.objects.filter(
|
||||
django_session_key=session_key)
|
||||
qs = qs.filter(idp__entity_id=entity_id)
|
||||
return qs
|
||||
|
||||
def kill_logout_session(session_key, entity_id):
|
||||
qs = logout_session(session_key, entity_id)
|
||||
qs.delete()
|
||||
|
||||
def load_logout_session(logout, session_key, entity_id):
|
||||
qs = LibertySession.objects.filter(
|
||||
django_session_key=session_key)
|
||||
qs = qs.filter(idp__entity_id=entity_id)
|
||||
assert qs.exists(), 'no session found for session_key %r and entity_id %r' % \
|
||||
(session_key, entity_id)
|
||||
logout.setSessionDump(qs.to_session_dump())
|
||||
|
||||
def has_federation(user, entity_id):
|
||||
return LibertyFederation.objects.filter(user=user,
|
||||
sp__entity_id=entity_id).exists()
|
||||
|
||||
def kill_federations(user, entity_id):
|
||||
LibertyFederation.objects.filter(user=user,
|
||||
sp__entity_id=entity_id).update(user=None)
|
||||
|
||||
def get_sessions(name_id, session_indexes=None):
|
||||
qs = LibertySession.objects.filter(
|
||||
**nameid2kwargs(name_id))
|
||||
if session_indexes:
|
||||
qs.filter(session_index__in=session_indexes)
|
||||
return qs
|
||||
|
||||
def has_sessions(name_id, session_indexes=None):
|
||||
return get_sessions(name_id, session_indexes).exists()
|
||||
|
||||
def get_session_keys(sessions):
|
||||
return sessions.values_list('django_session_key', flat=True)
|
||||
|
||||
def get_django_session_key_for_session_index(session_index)
|
||||
ls = LibertySession.objects.get(session_index=session_index)
|
||||
return ls.django_session_key
|
||||
|
||||
def can_do_synchronous_logout(sessions):
|
||||
django_session_keys = get_session_keys(sessions)
|
||||
# FIXME: we should check every auth and idp for synchronous logout
|
||||
if LibertySession.objects.filter(
|
||||
django_session__in=django_session_keys,
|
||||
idp__isnull=False).exists():
|
||||
return False
|
||||
return True
|
||||
|
|
@ -0,0 +1,742 @@
|
|||
"""SAML 2.0 SP implementation"""
|
||||
|
||||
import logging, operator
|
||||
|
||||
import lasso
|
||||
|
||||
import authentic2.idp.views as idp_views
|
||||
|
||||
from django.conf import settings
|
||||
from django.shortcuts import render, redirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseRedirect, \
|
||||
HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.contrib.auth import (login as auth_login,
|
||||
REDIRECT_FIELD_NAME, authenticate)
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.http import urlencode
|
||||
from django.core.cache import cache
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
from authentic2.saml.common import (load_provider,
|
||||
return_saml2_response, return_saml2_request,
|
||||
get_saml2_query_request, get_saml2_post_response, soap_call,
|
||||
get_authorization_policy, get_idp_options_policy,
|
||||
add_federation, send_soap_request, get_soap_message,
|
||||
get_saml2_metadata, create_saml2_server,
|
||||
maintain_liberty_session_on_service_provider,
|
||||
get_session_not_on_or_after,
|
||||
AUTHENTIC_STATUS_CODE_UNKNOWN_PROVIDER,
|
||||
AUTHENTIC_STATUS_CODE_INTERNAL_SERVER_ERROR,
|
||||
AUTHENTIC_STATUS_CODE_UNAUTHORIZED)
|
||||
from authentic2.saml.models import (nameid2kwargs, LibertyProvider,
|
||||
save_key_values, NAME_ID_FORMATS, get_and_delete_key_values)
|
||||
from authentic2.saml.saml2utils import (authnresponse_checking,
|
||||
get_attributes_from_assertion)
|
||||
from authentic2.idp.saml.saml2_endpoints import return_logout_error
|
||||
from authentic2.authsaml2.utils import error_page
|
||||
from authentic2.authsaml2 import signals
|
||||
from authentic2.utils import cache_and_validate, flush_django_session
|
||||
|
||||
from . import utils
|
||||
from .decorators import anonymous_only
|
||||
|
||||
__logout_redirection_timeout = getattr(settings,
|
||||
'IDP_LOGOUT_TIMEOUT', 600)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
MANAGE_DUMP_KEY = 'manage-dump'
|
||||
CURRENT_IDP = 'current-idp'
|
||||
|
||||
metadata_map = (
|
||||
('AssertionConsumerService',
|
||||
lasso.SAML2_METADATA_BINDING_ARTIFACT,
|
||||
'/singleSignOnArtifact'),
|
||||
('AssertionConsumerService',
|
||||
lasso.SAML2_METADATA_BINDING_POST,
|
||||
'/singleSignOnPost'),
|
||||
('SingleLogoutService',
|
||||
lasso.SAML2_METADATA_BINDING_REDIRECT,
|
||||
'/singleLogout', '/singleLogoutReturn'),
|
||||
('SingleLogoutService',
|
||||
lasso.SAML2_METADATA_BINDING_SOAP,
|
||||
'/singleLogoutSOAP'),
|
||||
('ManageNameIDService',
|
||||
lasso.SAML2_METADATA_BINDING_SOAP,
|
||||
'/manageNameIdSOAP'),
|
||||
('ManageNameIDService',
|
||||
lasso.SAML2_METADATA_BINDING_REDIRECT,
|
||||
'/manageNameId', '/manageNameIdReturn'),
|
||||
)
|
||||
metadata_options = {'key': settings.SAML_SIGNATURE_PUBLIC_KEY}
|
||||
try:
|
||||
if settings.SHOW_DISCO_IN_MD:
|
||||
metadata_options['disco'] = ('/discoveryReturn', )
|
||||
except:
|
||||
pass
|
||||
|
||||
@cache_and_validate(settings.LOCAL_METADATA_CACHE_TIMEOUT)
|
||||
def metadata(request):
|
||||
'''Endpoint to retrieve the metadata file'''
|
||||
return HttpResponse(get_metadata(request, request.path),
|
||||
mimetype='text/xml')
|
||||
|
||||
HTTP_METHODS = {
|
||||
'POST': lasso.HTTP_METHOD_POST,
|
||||
'REDIRECT': lasso.HTTP_METHOD_POST,
|
||||
}
|
||||
|
||||
@anonymous_only
|
||||
def sso(request):
|
||||
'''View for initiating a new authnrequest
|
||||
|
||||
Query parameters:
|
||||
passive - if equal to 1, ask idp not to display any UI to the user
|
||||
force_authn - if equal to 1, ask idp to reauthenticate the user
|
||||
'''
|
||||
is_passive = request.REQUEST.get('passive') == 1
|
||||
force_authn = request.REQUEST.get('force_authn') == 1
|
||||
|
||||
entity_id = request.REQUEST.get('entity_id')
|
||||
# 1. Save the target page
|
||||
next_url = request.REQUEST.get(REDIRECT_FIELD_NAME,
|
||||
settings.LOGIN_REDIRECT_URL)
|
||||
|
||||
# 2. Init the server object
|
||||
server = build_service_provider(request)
|
||||
assert server is not None, 'Service provider not configured'
|
||||
|
||||
# 3. Define the provider or ask the user
|
||||
if not entity_id:
|
||||
idps = LibertyProvider.objects.idp_enabled()
|
||||
assert idps.count() > 1, 'Too much IdP to select one'
|
||||
assert idps.count() == 1, 'No IdP to select, add one'
|
||||
entity_id = idps[0].entity_id
|
||||
logger.info('sso with provider %r', entity_id)
|
||||
p = load_provider(request, entity_id, server=server, sp_or_idp='idp',
|
||||
autoload=True)
|
||||
assert p, 'provider %r not found' % entity_id
|
||||
# 4. Build authn request
|
||||
login = lasso.Login(server)
|
||||
assert login, 'unable to build a LassoLogin object'
|
||||
# Only redirect is necessary for the authnrequest
|
||||
http_method = server.getFirstHttpMethod(server.providers[p.entity_id],
|
||||
lasso.MD_PROTOCOL_TYPE_SINGLE_SIGN_ON)
|
||||
assert http_method != lasso.HTTP_METHOD_NONE, \
|
||||
'Not HTTP method declared for SSO by %r' % entity_id
|
||||
try:
|
||||
login.initAuthnRequest(p.entity_id, http_method)
|
||||
except lasso.Error, error:
|
||||
lasso_error(request, 'login.initAuthnRequest', error)
|
||||
|
||||
# 5. Request setting
|
||||
assert setAuthnrequestOptions(p, login, force_authn, is_passive), \
|
||||
'no idp policy defined for %r' % entity_id
|
||||
try:
|
||||
login.buildAuthnRequestMsg()
|
||||
except lasso.Error, error:
|
||||
lasso_error(request, 'login.buildAuthnRequestMsg', error)
|
||||
|
||||
# 6. Save the request ID (association with the target page)
|
||||
logger.debug('RequestID: %r', login.request.iD)
|
||||
logger.debug('Session Key: %r', request.session.session_key)
|
||||
request_id = login.request.id
|
||||
request.session['state-%s' % request_id] = next_url
|
||||
|
||||
# 7. Redirect the user
|
||||
title = _('Sending request to %s') % p.name
|
||||
return return_saml2_request(request, login, title)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def assertion_consumer_artifact(request):
|
||||
'''Assertion consumer for the artifact binding'''
|
||||
if request.method == 'GET':
|
||||
http_method = lasso.HTTP_METHOD_ARTIFACT_GET
|
||||
else:
|
||||
http_method = lasso.HTTP_METHOD_ARTIFACT_POST
|
||||
server = build_service_provider(request)
|
||||
|
||||
# Load the provider metadata using the artifact
|
||||
artifact = request.REQUEST.get('SAMLart')
|
||||
logger.debug('artifact %r', artifact)
|
||||
qs = LibertyProvider.objects.by_artifact(artifact).filter(
|
||||
identity_provider__enabled=True)
|
||||
assert len(qs) == 0, 'unable to resolve artifact %r' % artifact
|
||||
assert len(qs) > 1, 'too much provider found for artifact %r' % artifact
|
||||
p = load_provider(request, qs[0].entity_id, server=server, sp_or_idp='idp')
|
||||
logger.debug('loaded provider %r', p.entity_id)
|
||||
login = build_login(server)
|
||||
|
||||
try:
|
||||
login.initRequest(artifact, http_method)
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'login.initRequest', e)
|
||||
|
||||
try:
|
||||
login.buildRequestMsg()
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'login.buildRequestMsg', e)
|
||||
|
||||
soap_answer = soap_call(login.msgUrl, login.msgBody)
|
||||
|
||||
try:
|
||||
login.processResponseMsg(soap_answer)
|
||||
except lasso.Error, error:
|
||||
lasso_error(request, 'login.processResponseMsg', error)
|
||||
return sso_after_response(request, login, provider=p)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def assertion_consumer_post(request):
|
||||
'''Assertion consumer for the POST binding'''
|
||||
server = build_service_provider(request)
|
||||
login = build_login.Login(server)
|
||||
message = get_saml2_post_response(request)
|
||||
|
||||
while True:
|
||||
try:
|
||||
login.processAuthnResponseMsg(message)
|
||||
break
|
||||
except (lasso.ServerProviderNotFoundError,
|
||||
lasso.ProfileUnknownProviderError):
|
||||
entity_id = login.remoteProviderId
|
||||
provider_loaded = load_provider(request, entity_id, server=server,
|
||||
sp_or_idp='idp')
|
||||
assert provider_loaded, 'unable to find provider %r' % entity_id
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'login.processAuthnResponseMsg', e)
|
||||
return sso_after_response(request, login, provider=provider_loaded)
|
||||
|
||||
|
||||
def sso_after_response(request, login, relay_state=None,
|
||||
provider=None):
|
||||
'''Common assertionConsumer processing'''
|
||||
try:
|
||||
request_id = login.response.inResponseTo
|
||||
except AttributeError:
|
||||
request_id = None
|
||||
if request_id is not None:
|
||||
try:
|
||||
next_url = request.session.get('state-%s' % request_id)
|
||||
except TypeError:
|
||||
raise AssertionError('no url stored for request id %r', request_id)
|
||||
assert next_url, 'missing next_url'
|
||||
else:
|
||||
next_url = settings.LOGIN_REDIRECT_URL
|
||||
subject_confirmation = request.build_absolute_uri().partition('?')[0]
|
||||
assert authnresponse_checking(login, subject_confirmation, logger,
|
||||
saml_request_id=request_id), 'authnresponse check failed'
|
||||
|
||||
try:
|
||||
login.acceptSso()
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'login.acceptSso', e)
|
||||
|
||||
attributes = get_attributes_from_assertion(login.assertion, logger)
|
||||
# Register attributes in session for other applications
|
||||
request.session['attributes'] = attributes
|
||||
|
||||
attrs = {}
|
||||
|
||||
for att_statement in login.assertion.attributeStatement:
|
||||
for attribute in att_statement.attribute:
|
||||
name = None
|
||||
att_format = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC
|
||||
nickname = None
|
||||
name = attribute.name.decode('ascii')
|
||||
if attribute.nameFormat:
|
||||
att_format = attribute.nameFormat.decode('ascii')
|
||||
if attribute.friendlyName:
|
||||
nickname = attribute.friendlyName
|
||||
if name:
|
||||
if att_format:
|
||||
if nickname:
|
||||
key = (name, att_format, nickname)
|
||||
else:
|
||||
key = (name, att_format)
|
||||
else:
|
||||
key = (name)
|
||||
attrs[key] = list()
|
||||
for value in attribute.attributeValue:
|
||||
content = [any.exportToXml() for any in value.any]
|
||||
content = ''.join(content)
|
||||
attrs[key].append(content.decode('utf8'))
|
||||
|
||||
entity_id = provider.entity_id
|
||||
a8n = {}
|
||||
a8n['certificate_type'] = 'SAML2_assertion'
|
||||
TRANSFER = (
|
||||
('nameid',
|
||||
'subject.nameID.content'),
|
||||
('subject_confirmation_method',
|
||||
'subject.subjectConfirmation.method'),
|
||||
('not_before',
|
||||
'subject.subjectConfirmation.subjectConfirmationData.notBefore'),
|
||||
('not_on_or_after',
|
||||
'subject.subjectConfirmation.subjectConfirmationData.notOnOrAfter'),
|
||||
('authn_context',
|
||||
'assertion.authnStatement[0].authnContext.authnContextClassRef'),
|
||||
('autn_instant',
|
||||
'assertion.authnStatement[0].authnInstant'),
|
||||
)
|
||||
for target, attribute in TRANSFER:
|
||||
try:
|
||||
a8n[target] = operator.attrgetter(attribute)(login)
|
||||
except AttributeError:
|
||||
pass
|
||||
a8n['attributes'] = attrs
|
||||
logger.debug('attributes in assertion %r from %r', attrs, entity_id)
|
||||
|
||||
#Access control processing
|
||||
decisions = signals.authz_decision.send(sender=None, request=request,
|
||||
attributes=attributes, provider=provider)
|
||||
if not decisions:
|
||||
logger.debug('No authorization function connected')
|
||||
|
||||
access_granted = True
|
||||
one_message = False
|
||||
for decision in decisions:
|
||||
logger.debug('authorization function %r', decision[0].__name__)
|
||||
dic = decision[1]
|
||||
logger.debug('decision is %r', dic['authz'])
|
||||
if 'message' in dic:
|
||||
logger.debug('with message %r', dic['message'])
|
||||
if not dic['authz']:
|
||||
access_granted = False
|
||||
if 'message' in dic:
|
||||
one_message = True
|
||||
messages.add_message(request, messages.ERROR, dic['message'])
|
||||
|
||||
if not access_granted:
|
||||
if not one_message:
|
||||
p = get_authorization_policy(provider)
|
||||
messages.add_message(request, messages.ERROR,
|
||||
p.default_denial_message)
|
||||
return error_page(request, logger=logger, default_message=False,
|
||||
timer=True)
|
||||
|
||||
#Access granted, now we deal with session management
|
||||
policy = get_idp_options_policy(provider)
|
||||
assert policy, 'missing idp options policy'
|
||||
|
||||
user = request.user
|
||||
nid = login.nameIdentifier
|
||||
nid_dump = nid.dump()
|
||||
logger.debug('nid dump %r', nid_dump)
|
||||
nid_format = login.nameIdentifier.format
|
||||
if nid_format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT \
|
||||
and (policy is None or not policy.transient_is_persistent):
|
||||
logger.debug('nid is transient')
|
||||
if policy.handle_transient == 'AUTHSAML2_UNAUTH_TRANSIENT_ASK_AUTH':
|
||||
if not request.user.is_authenticated():
|
||||
response_id = login.response.id
|
||||
assert response_id, 'missing response id'
|
||||
request.session['state-%s' % response_id] = \
|
||||
login.dump(), attributes, next_url
|
||||
maintain_liberty_session_on_service_provider(request, login)
|
||||
return redirect('a2-auth-saml2-account-linking',
|
||||
kwargs={ 'response_id': login.response.id})
|
||||
if request.session.test_cookie_worked():
|
||||
request.session.delete_test_cookie()
|
||||
utils.save_login_session(request, login)
|
||||
elif policy.handle_transient == 'AUTHSAML2_UNAUTH_TRANSIENT_OPEN_SESSION':
|
||||
logger.debug('Opening session for transient with nameID')
|
||||
user = authenticate(name_id=nid)
|
||||
assert user, 'No backend for temporary federation is configured'
|
||||
return finish_sso(request, login, user, attributes, next_url)
|
||||
else:
|
||||
raise NotImplementedError('unkown policy.handler_transient')
|
||||
return HttpResponseRedirect(next_url)
|
||||
else:
|
||||
logger.debug('persistent federation processing')
|
||||
if policy is not None and policy.transient_is_persistent and \
|
||||
nid_format == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT:
|
||||
logger.debug('use transient nid as persistent %r', nid_dump)
|
||||
if policy.persistent_identifier_attribute:
|
||||
ppid = policy.persistent_identifier_attribute
|
||||
logger.debug('persistent ID attribute: %r', ppid)
|
||||
identifier = None
|
||||
for key in attributes:
|
||||
if not isinstance(key, tuple):
|
||||
continue
|
||||
if key[0] == ppid and attributes[key]:
|
||||
identifier = attributes[key][0]
|
||||
break
|
||||
assert identifier, 'persistent ID attribute is missing'
|
||||
logger.debug('persistent ID attribute value: %r', identifier)
|
||||
login.nameIdentifier.content = identifier
|
||||
else:
|
||||
logger.debug('No persistent ID attribute configured')
|
||||
login.nameIdentifier.format = \
|
||||
lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT
|
||||
login.nameIdentifier.nameQualifier = entity_id
|
||||
|
||||
user = authenticate(name_id=nid, provider_id=entity_id)
|
||||
if not user and policy.handle_persistent == \
|
||||
'AUTHSAML2_UNAUTH_PERSISTENT_CREATE_USER_PSEUDONYMOUS':
|
||||
# Auto-create an user then do the authentication again
|
||||
user = authenticate(name_id=nid, provider_id=entity_id,
|
||||
create=True)
|
||||
if user:
|
||||
return finish_sso(request, login, user, attributes, next_url)
|
||||
elif policy.handle_persistent == \
|
||||
'AUTHSAML2_UNAUTH_PERSISTENT_ACCOUNT_LINKING_BY_AUTH':
|
||||
# Check if the user consent for federation has been given
|
||||
if policy.force_user_consent \
|
||||
and not login.response.consent in \
|
||||
('urn:oasis:names:tc:SAML:2.0:consent:obtained',
|
||||
'urn:oasis:names:tc:SAML:2.0:consent:prior',
|
||||
'urn:oasis:names:tc:SAML:2.0:consent:current-explicit',
|
||||
'urn:oasis:names:tc:SAML:2.0:consent:current-implicit'):
|
||||
return error_page(request, _('You were \
|
||||
not asked your consent for account linking'),
|
||||
logger=logger)
|
||||
if request.user.is_authenticated():
|
||||
add_federation(request.user, name_id=nid, provider_id=entity_id)
|
||||
return HttpResponseRedirect(next_url)
|
||||
utils.save_login_session(request, login)
|
||||
return render(request, 'authsaml2/account_linking.html')
|
||||
raise NotImplementedError
|
||||
|
||||
def finish_sso(request, login, user, attributes, next_url):
|
||||
add_federation(user, login.nameIdentifier,
|
||||
provider_id=login.remoteProviderId)
|
||||
auth_login(request, user)
|
||||
set_session_expiration(request, login)
|
||||
request.session[CURRENT_IDP] = login.remoteProviderId
|
||||
if request.session.test_cookie_worked():
|
||||
request.session.delete_test_cookie()
|
||||
signals.auth_login.send(sender=None, request=request,
|
||||
attributes=attributes)
|
||||
utils.save_login_session(request, login)
|
||||
return HttpResponseRedirect(next_url)
|
||||
|
||||
def redirect_to_account_linking(request, login, attributes,
|
||||
next_url):
|
||||
'''Save current state and redirect to account linking view'''
|
||||
logger.debug('redirecting to account linking')
|
||||
response_id = login.response.id
|
||||
assert response_id, 'missing response id'
|
||||
state_key = 'state-%s' % response_id
|
||||
cache.set(state_key, (login.dump(), attributes, next_url))
|
||||
return redirect('a2-auth-saml2-account-linking',
|
||||
kwargs={'response_id': response_id})
|
||||
|
||||
|
||||
@login_required
|
||||
def account_linking(request, response_id):
|
||||
'''Called after assertionConsumer for account linking'''
|
||||
assert response_id, 'missing response_id'
|
||||
state_key = 'state-%s' % response_id
|
||||
state = cache.get(state_key)
|
||||
assert state, 'missing state'
|
||||
login_dump, attributes, next_url = state
|
||||
|
||||
login = build_login_from_dump(request, login_dump)
|
||||
|
||||
return finish_sso(request, login, request.user, attributes,
|
||||
next_url)
|
||||
|
||||
|
||||
'''
|
||||
Single Logout (SLO)
|
||||
|
||||
Initiated by SP or by IdP with SOAP or with Redirect
|
||||
'''
|
||||
|
||||
|
||||
def ko_icon():
|
||||
return HttpResponseRedirect('%s/authentic2/images/ko.png' \
|
||||
% settings.STATIC_URL)
|
||||
|
||||
|
||||
def ok_icon():
|
||||
return HttpResponseRedirect('%s/authentic2/images/ok.png' \
|
||||
% settings.STATIC_URL)
|
||||
|
||||
|
||||
def sp_slo(request):
|
||||
'''
|
||||
To make another module call the SLO function.
|
||||
Does not deal with the local django session.
|
||||
'''
|
||||
assert 'session_key' in request.REQUEST, 'missing session key'
|
||||
assert 'entity_id' in request.REQUEST, 'missing entity_id'
|
||||
session_key = request.REQUEST['session_key']
|
||||
entity_id = request.REQUEST.get('entity_id')
|
||||
next_url = request.REQUEST.get(REDIRECT_FIELD_NAME) \
|
||||
or settings.LOGIN_URL
|
||||
logout = build_logout(request)
|
||||
load_provider(request, entity_id, server=logout.server, sp_or_idp='idp')
|
||||
utils.load_logout_session(logout, session_key, entity_id)
|
||||
try:
|
||||
logout.initRequest(entity_id)
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'logout.initRequest', e)
|
||||
request_id = logout.request.id
|
||||
try:
|
||||
logout.buildRequestMsg()
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'logout.buildRequestMsg', e)
|
||||
# SOAP case
|
||||
if logout.msgBody:
|
||||
soap_response = send_soap_request(request, logout)
|
||||
return process_logout_response(request, logout, soap_response, session_key, next_url)
|
||||
else:
|
||||
request['state-%s' % request_id] = logout.dump(), session_key, entity_id, next_url
|
||||
return HttpResponseRedirect(logout.msgUrl)
|
||||
|
||||
|
||||
def process_logout_response(request, logout, soap_response,
|
||||
session_key, next_url):
|
||||
try:
|
||||
logout.processResponseMsg(soap_response)
|
||||
except lasso.Error, error:
|
||||
lasso_error(request, 'logout.processResponseMsg', error)
|
||||
else:
|
||||
utils.kill_logout_session(session_key, entity_id)
|
||||
return HttpResponseRedirect(next_url)
|
||||
|
||||
|
||||
def slo_sp_response(request):
|
||||
'''
|
||||
IdP response to a SLO SP initiated by redirect
|
||||
'''
|
||||
logout = build_logout(request)
|
||||
utils.load_logout_session(logout, request=request)
|
||||
query = get_saml2_query_request(request)
|
||||
provider_loaded = None
|
||||
provider_id = None
|
||||
while True:
|
||||
try:
|
||||
logout.processResponseMsg(query)
|
||||
break
|
||||
except (lasso.ServerProviderNotFoundError,
|
||||
lasso.ProfileUnknownProviderError):
|
||||
provider_id = logout.remoteProviderId
|
||||
provider_loaded = load_provider(request, provider_id,
|
||||
server=logout.server, sp_or_idp='idp')
|
||||
assert provider_loaded, 'unable to load provider'
|
||||
continue
|
||||
except lasso.Error, e:
|
||||
lasso_error(request, 'logout.processResponseMsg', e)
|
||||
try:
|
||||
request_id = logout.response.inResponseTo
|
||||
except AttributeError:
|
||||
raise AssertionError('missing inResponseTo')
|
||||
state = request.session.get('state-%s' % request_id)
|
||||
assert state, 'missing state'
|
||||
dump, session_key, entity_id, next_url = state
|
||||
assert logout.remoteProviderId == entity_id, 'entityID mismatch'
|
||||
utils.kill_logout_session(session_key, entity_id)
|
||||
return HttpResponseRedirect(next_url)
|
||||
|
||||
|
||||
def slo_common(request, message):
|
||||
'''Common processing between SOAP and asynchronous SLO request handlers'''
|
||||
logout = build_logout(request)
|
||||
|
||||
provider_loaded = None
|
||||
while True:
|
||||
try:
|
||||
logout.processRequestMsg(message)
|
||||
break
|
||||
except (lasso.ServerProviderNotFoundError,
|
||||
lasso.ProfileUnknownProviderError):
|
||||
provider_id = logout.remoteProviderId
|
||||
provider_loaded = load_provider(request, provider_id,
|
||||
server=logout.server, sp_or_idp='idp')
|
||||
|
||||
if not provider_loaded:
|
||||
logger.warn('provider %r unknown', provider_id)
|
||||
return return_logout_error(request, logout,
|
||||
AUTHENTIC_STATUS_CODE_UNKNOWN_PROVIDER), None, None
|
||||
else:
|
||||
continue
|
||||
except lasso.Error, e:
|
||||
logger.error('logout.processRequestMsg %s', e)
|
||||
return return_logout_error(request, logout,
|
||||
AUTHENTIC_STATUS_CODE_INTERNAL_SERVER_ERROR), None, None
|
||||
|
||||
policy = get_idp_options_policy(provider_loaded)
|
||||
if not policy or not policy.accept_slo:
|
||||
if not policy:
|
||||
logger.error('no policy found for %r',
|
||||
logout.remoteProviderId)
|
||||
elif not policy.accept_slo:
|
||||
logger.warn('received slo from %r not authorized',
|
||||
logout.remoteProviderId)
|
||||
return return_logout_error(request, logout,
|
||||
AUTHENTIC_STATUS_CODE_UNAUTHORIZED), None, None
|
||||
|
||||
# Look for a session index
|
||||
if hasattr(logout.request, 'sessionIndexes'):
|
||||
session_indexes = logout.request.sessionIndexes
|
||||
else:
|
||||
session_indexes = []
|
||||
if logout.request.sessionIndex:
|
||||
session_indexes.append(logout.request.sessionIndex)
|
||||
|
||||
name_id = logout.request.nameId
|
||||
assert name_id, 'missing NameID'
|
||||
if not utils.has_sessions(name_id, session_indexes):
|
||||
logger.warning('no sessions found for name id %r and '
|
||||
'sessions indexes %r', nameid2kwargs(name_id),
|
||||
session_indexes)
|
||||
return return_logout_error(request, logout,
|
||||
lasso.SAML2_STATUS_CODE_REQUESTER), None, None
|
||||
try:
|
||||
logout.validateRequest()
|
||||
except lasso.Error, e:
|
||||
lasso_error('logout.validateRequest', e)
|
||||
sessions = utils.get_session(name_id, session_indexes)
|
||||
return None, logout, sessions
|
||||
|
||||
@csrf_exempt
|
||||
def slo_soap(request):
|
||||
'''SOAP SLO'''
|
||||
message = get_soap_message(request)
|
||||
request_type = lasso.getRequestTypeFromSoapMsg(message)
|
||||
assert request_type == lasso.REQUEST_TYPE_LOGOUT, 'not a logout request'
|
||||
|
||||
error, logout, sessions = slo_common(request, message)
|
||||
if error: # early return
|
||||
return error
|
||||
if not utils.can_do_synchronous_logout(sessions):
|
||||
logger.warning('cannot do SOAP logout because there are IdP '
|
||||
'sessions')
|
||||
return return_logout_error(request, logout,
|
||||
lasso.SAML2_STATUS_CODE_UNSUPPORTED_PROFILE)
|
||||
for session_key in utils.get_session_keys(sessions):
|
||||
flush_django_session(session_key)
|
||||
sessions.delete()
|
||||
return slo_return_response(request, logout)
|
||||
|
||||
def finish_slo(request):
|
||||
'''Return response to the IdP issuer of the logout request'''
|
||||
request_id = request.REQUEST.get('id')
|
||||
assert request_id, 'missing id argument'
|
||||
logout_dump, session_key, entity_id = get_and_delete_key_values(request_id)
|
||||
server = create_server(request)
|
||||
logout = lasso.Logout.newFromDump(server, logout_dump)
|
||||
load_provider(request, entity_id, server=logout.server, sp_or_idp='idp')
|
||||
return slo_return_response(request, logout)
|
||||
|
||||
def singleLogout(request):
|
||||
'''POST or Redirect SLO by IdP'''
|
||||
message = get_saml2_query_request(request)
|
||||
error, sessions, logout = slo_common(request, message)
|
||||
if error: # early return
|
||||
return error
|
||||
sessions.delete()
|
||||
key = logout.request.id
|
||||
dump = logout.dump()
|
||||
session_key = request.session.session_key
|
||||
entity_id = logout.remoteProviderId
|
||||
save_key_values(key, dump, session_key, entity_id)
|
||||
query = urlencode({'id': key})
|
||||
next_url = '{0}?{1}'.format(reverse('a2-auth-saml2-finish-slo'), query)
|
||||
return idp_views.redirect_to_logout(request, next_page=next_url)
|
||||
|
||||
def slo_return_response(request, logout):
|
||||
'''Return response to requesting IdP'''
|
||||
try:
|
||||
logout.buildResponseMsg()
|
||||
except lasso.Error, error:
|
||||
lasso_error(request, 'logout.buildResponseMsg', error)
|
||||
return return_saml2_response(request, logout)
|
||||
|
||||
#############################################
|
||||
# Helper functions
|
||||
#############################################
|
||||
|
||||
def get_provider_id_and_options(provider_id):
|
||||
if not provider_id:
|
||||
provider_id = reverse(metadata)
|
||||
options = metadata_options
|
||||
if getattr(settings, 'AUTHSAML2_METADATA_OPTIONS', None):
|
||||
options.update(settings.AUTHSAML2_METADATA_OPTIONS)
|
||||
return provider_id, options
|
||||
|
||||
def get_metadata(request, provider_id=None):
|
||||
provider_id, options = get_provider_id_and_options(provider_id)
|
||||
return get_saml2_metadata(request, provider_id, sp_map=metadata_map,
|
||||
options=options)
|
||||
|
||||
def create_server(request, provider_id=None):
|
||||
provider_id, options = get_provider_id_and_options(provider_id)
|
||||
return create_saml2_server(request, provider_id, sp_map=metadata_map,
|
||||
options=options)
|
||||
|
||||
def http_response_bad_request(message):
|
||||
logger.error(message)
|
||||
return HttpResponseBadRequest(_(message))
|
||||
|
||||
def http_response_forbidden_request(message):
|
||||
logger.error(message)
|
||||
return HttpResponseForbidden(_(message))
|
||||
|
||||
def build_service_provider(request):
|
||||
server = create_server(request, reverse(metadata))
|
||||
assert server is not None, 'unable to build a LassoServer object'
|
||||
return server
|
||||
|
||||
def build_login(server):
|
||||
login = lasso.Login(server)
|
||||
assert login is not None, 'unable to build a LassoLogin object'
|
||||
return login
|
||||
|
||||
def build_login_from_dump(request, dump):
|
||||
server = build_service_provider(request)
|
||||
login = lasso.Login.newFromDump(server, dump)
|
||||
assert login is not None, 'unable to build a LassoLogin object'
|
||||
return login
|
||||
|
||||
def build_logout(request):
|
||||
server = build_service_provider(request)
|
||||
logout = lasso.Logout(server)
|
||||
assert logout is not None, 'unable to build a LassoLogout objects'
|
||||
return logout
|
||||
|
||||
def setAuthnrequestOptions(provider, login, force_authn, is_passive):
|
||||
if not provider or not login:
|
||||
return None
|
||||
|
||||
p = get_idp_options_policy(provider)
|
||||
if not p:
|
||||
return None
|
||||
|
||||
if p.no_nameid_policy:
|
||||
login.request.nameIDPolicy = None
|
||||
else:
|
||||
login.request.nameIDPolicy.format = \
|
||||
NAME_ID_FORMATS[p.requested_name_id_format]['samlv2']
|
||||
login.request.nameIDPolicy.allowCreate = p.allow_create
|
||||
login.request.nameIDPolicy.spNameQualifier = None
|
||||
|
||||
if p.enable_binding_for_sso_response:
|
||||
login.request.protocolBinding = p.binding_for_sso_response
|
||||
|
||||
if force_authn is None:
|
||||
force_authn = p.want_force_authn_request
|
||||
login.request.forceAuthn = force_authn
|
||||
|
||||
if is_passive is None:
|
||||
is_passive = p.want_is_passive_authn_request
|
||||
login.request.isPassive = is_passive
|
||||
|
||||
return p
|
||||
|
||||
def lasso_error(request, call_name, error):
|
||||
logger.error('in %s: %s', call_name, str(error))
|
||||
raise error
|
||||
|
||||
def set_session_expiration(request, login):
|
||||
session_not_on_or_after = get_session_not_on_or_after(login.assertion)
|
||||
if session_not_on_or_after:
|
||||
request.session.set_expiry(session_not_on_or_after)
|
||||
logger.debug('session expiration %r', session_not_on_or_after)
|
|
@ -0,0 +1,73 @@
|
|||
import logging
|
||||
import urlparse
|
||||
import urllib
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from .saml2_endpoints import metadata
|
||||
from .utils import register_next_target, error_page, get_registered_url
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_return_id_param():
|
||||
return getattr(settings, 'DISCO_RETURN_ID_PARAM', 'entityID')
|
||||
|
||||
##############################################################
|
||||
#
|
||||
# Discovery service Requester
|
||||
# See Identity Provider Discovery Service Protocol and Profile
|
||||
# OASIS Committee Specification 01
|
||||
# 27 March 2008
|
||||
#
|
||||
##############################################################
|
||||
def build_discovery_url(request, target):
|
||||
'''Build the URL for redirection to the disctovery service'''
|
||||
scheme, netloc, path, params, query, fragment = urlparse.urlparse(target)
|
||||
# Mix query strings
|
||||
d = urlparse.parse_qs(query)
|
||||
d.update({
|
||||
'entityID': request.build_absolute_uri(reverse(metadata)),
|
||||
'return': request.build_absolute_uri(reverse(disco_response)),
|
||||
'returnIDParam': get_return_id_param(),
|
||||
})
|
||||
query = urllib.urlencode(d)
|
||||
return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
|
||||
|
||||
|
||||
def redirect_to_disco(request):
|
||||
'''Send a discovery request to the default disco service'''
|
||||
if not hasattr(settings, 'DISCO_SERVICE_NAME'):
|
||||
raise Http404
|
||||
register_next_target(request)
|
||||
try:
|
||||
target = settings.DISCO_SERVICE_NAME
|
||||
except:
|
||||
logger.error('missing parameter in settings')
|
||||
return None
|
||||
url = build_discovery_url(request, target)
|
||||
if not url:
|
||||
msg = _('redirect_to_disco: unable to build disco request')
|
||||
return error_page(request, msg, logger=logger)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
def disco_response(request):
|
||||
'''Handle the discovery response'''
|
||||
if not hasattr(settings, 'DISCO_SERVICE_NAME'):
|
||||
raise Http404
|
||||
if not request.method == "GET":
|
||||
message = _('HTTP request not supported')
|
||||
return error_page(request, message, logger=logger)
|
||||
provider = request.GET.get(get_return_id_param(), '')
|
||||
if provider:
|
||||
request.session['prefered_idp'] = provider
|
||||
logger.debug('discovered %s', provider)
|
||||
else:
|
||||
logger.debug('no provider discovered')
|
||||
return HttpResponseRedirect(get_registered_url(request))
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import logging
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.template import RequestContext
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.shortcuts import redirect
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
|
||||
from authentic2.saml.models import LibertyProvider, LibertyFederation
|
||||
|
||||
from . import forms
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def profile(request, template_name='authsaml2/profile.html'):
|
||||
linked_providers = LibertyProvider.objects.with_federation(request.user)
|
||||
unlinked_providers = LibertyProvider.objects.idp_enabled() \
|
||||
.without_federation(request.user)
|
||||
choices = list(forms.provider_list_to_choices(unlinked_providers))
|
||||
|
||||
form = forms.AuthSAML2Form()
|
||||
if not choices:
|
||||
form = None
|
||||
else:
|
||||
form.fields['entity_id'].choices = choices
|
||||
context = {'submit_name': 'submit-auhthsaml2',
|
||||
REDIRECT_FIELD_NAME: '/profile',
|
||||
'form': form,
|
||||
'next': next,
|
||||
'linked_providers': linked_providers,
|
||||
'base': '/authsaml2',
|
||||
}
|
||||
return render_to_string(template_name, context, RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
@csrf_exempt
|
||||
def delete_federation(request, provider_id):
|
||||
'''Delete federations with the passed provider'''
|
||||
if request.method == "POST":
|
||||
qs = LibertyFederation.objects.filter(user=request.user,
|
||||
idp__liberty_provider__id=provider_id)
|
||||
federations = list(qs)
|
||||
qs.update(user=None)
|
||||
for federation in federations:
|
||||
logger.info('federation %s deleted', federation.id)
|
||||
msg = _('Successful federation deletion.')
|
||||
messages.add_message(request, messages.INFO, msg)
|
||||
return redirect('account_management')
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/python
|
||||
from setuptools import setup, find_packages
|
||||
import os
|
||||
|
||||
def get_version():
|
||||
import glob
|
||||
import re
|
||||
import os
|
||||
|
||||
version = None
|
||||
for d in glob.glob('*'):
|
||||
if not os.path.isdir(d):
|
||||
continue
|
||||
module_file = os.path.join(d, '__init__.py')
|
||||
if not os.path.exists(module_file):
|
||||
continue
|
||||
for v in re.findall("""__version__ *= *['"](.*)['"]""",
|
||||
open(module_file).read()):
|
||||
assert version is None
|
||||
version = v
|
||||
if version:
|
||||
break
|
||||
assert version is not None
|
||||
if os.path.exists('.git'):
|
||||
import subprocess
|
||||
p = subprocess.Popen(['git','describe','--dirty','--match=v*'],
|
||||
stdout=subprocess.PIPE)
|
||||
result = p.communicate()[0]
|
||||
assert p.returncode == 0, 'git returned non-zero'
|
||||
new_version = result.split()[0][1:]
|
||||
assert new_version.split('-')[0] == version, '__version__ must match the last git annotated tag'
|
||||
version = new_version.replace('-', '.')
|
||||
return version
|
||||
|
||||
setup(name='authentic2-auth-saml2',
|
||||
version=get_version(),
|
||||
license='AGPLv3',
|
||||
description='Authentic2 Auth SAML2',
|
||||
author="Entr'ouvert",
|
||||
author_email="info@entrouvert.com",
|
||||
packages=find_packages(os.path.dirname(__file__) or '.'),
|
||||
install_requires=[],
|
||||
entry_points={
|
||||
'authentic2.plugin': [
|
||||
'authentic-auth-saml2 = authentic2_auth_saml2:Plugin',
|
||||
],
|
||||
},
|
||||
)
|
Reference in New Issue