use MiddlewareMixin on middleware (#36509)

Remove OPENED_SESSION_COOKIE_DOMAIN which has no use.
This commit is contained in:
Benjamin Dauvergne 2019-09-29 11:20:44 +02:00
parent 83abc78605
commit 7095b1368b
6 changed files with 72 additions and 25 deletions

View File

@ -31,7 +31,6 @@ class AppSettings(object):
'DEFAULT_ASSERTION_CONSUMER_BINDING': 'post', # or artifact 'DEFAULT_ASSERTION_CONSUMER_BINDING': 'post', # or artifact
'VERIFY_SSL_CERTIFICATE': True, 'VERIFY_SSL_CERTIFICATE': True,
'OPENED_SESSION_COOKIE_NAME': None, 'OPENED_SESSION_COOKIE_NAME': None,
'OPENED_SESSION_COOKIE_DOMAIN': None,
'ORGANIZATION': None, 'ORGANIZATION': None,
'CONTACT_PERSONS': [], 'CONTACT_PERSONS': [],
'TRANSIENT_FEDERATION_ATTRIBUTE': None, 'TRANSIENT_FEDERATION_ATTRIBUTE': None,

27
mellon/compat.py Normal file
View File

@ -0,0 +1,27 @@
# django-mellon - SAML2 authentication for Django
# Copyright (C) 2014-2019 Entr'ouvert
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import django
if django.VERSION < (1, 11, 0):
from django.core.urlresolvers import reverse
MiddlewareClass = object
is_authenticated = lambda user: user.is_authenticated()
else:
from django.urls import reverse
from django.utils.deprecation import MiddlewareMixin
MiddlewareClass = MiddlewareMixin
is_authenticated = lambda user: user.is_authenticated

View File

@ -19,12 +19,12 @@ from django.utils.http import urlencode
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from . import app_settings, utils from . import app_settings, utils
from .compat import reverse from .compat import reverse, MiddlewareClass, is_authenticated
PASSIVE_TRIED_COOKIE = 'MELLON_PASSIVE_TRIED' PASSIVE_TRIED_COOKIE = 'MELLON_PASSIVE_TRIED'
class PassiveAuthenticationMiddleware(object): class PassiveAuthenticationMiddleware(MiddlewareClass):
def process_response(self, request, response): def process_response(self, request, response):
# When unlogged remove the PASSIVE_TRIED cookie # When unlogged remove the PASSIVE_TRIED cookie
if app_settings.OPENED_SESSION_COOKIE_NAME \ if app_settings.OPENED_SESSION_COOKIE_NAME \
@ -47,26 +47,19 @@ class PassiveAuthenticationMiddleware(object):
return return
if not app_settings.OPENED_SESSION_COOKIE_NAME: if not app_settings.OPENED_SESSION_COOKIE_NAME:
return return
if hasattr(request, 'user') and request.user.is_authenticated(): if hasattr(request, 'user') and is_authenticated(request.user):
return return
if PASSIVE_TRIED_COOKIE in request.COOKIES: if PASSIVE_TRIED_COOKIE in request.COOKIES:
return return
if app_settings.OPENED_SESSION_COOKIE_NAME in request.COOKIES: if app_settings.OPENED_SESSION_COOKIE_NAME not in request.COOKIES:
# get the common domain or guess return
common_domain = app_settings.OPENED_SESSION_COOKIE_DOMAIN # all is good, try passive login
if not common_domain: params = {
host = request.get_host() 'next': request.build_absolute_uri(),
# accept automatic common domain selection if domain has at least three components 'passive': '',
# and is not an IP address }
if not host.count('.') > 1 or host.replace('.', '').isdigit(): url = reverse('mellon_login') + '?%s' % urlencode(params)
return response = HttpResponseRedirect(url)
common_domain = request.get_host().split('.', 1)[1] # prevent loops
params = { response.set_cookie(PASSIVE_TRIED_COOKIE, value='1', max_age=None)
'next': request.build_absolute_uri(), return response
'passive': '',
}
url = reverse('mellon_login') + '?%s' % urlencode(params)
response = HttpResponseRedirect(url)
# prevent loops
response.set_cookie(PASSIVE_TRIED_COOKIE, value='1', max_age=None)
return response

View File

@ -373,3 +373,31 @@ def test_sso_slo_pass_login_hints_backoffice(db, app, idp, caplog, sp_settings):
login_hints = root.findall('.//{https://www.entrouvert.com/}login-hint') login_hints = root.findall('.//{https://www.entrouvert.com/}login-hint')
assert len(login_hints) == 1, 'missing login hint' assert len(login_hints) == 1, 'missing login hint'
assert login_hints[0].text == 'backoffice', 'login hint is not backoffice' assert login_hints[0].text == 'backoffice', 'login hint is not backoffice'
def test_middleware_mixin_first_time(db, app, idp, caplog, settings):
settings.MELLON_OPENED_SESSION_COOKIE_NAME = 'IDP_SESSION'
assert 'MELLON_PASSIVE_TRIED' not in app.cookies
# webtest-lint is against unicode
app.set_cookie(str('IDP_SESSION'), str('1'))
response = app.get('/', status=302)
assert urlparse.urlparse(response.location).path == '/login/'
assert (urlparse.parse_qs(urlparse.urlparse(response.location).query, keep_blank_values=True)
== {'next': ['http://testserver/'], 'passive': ['']})
# simulate closing of session at IdP
app.cookiejar.clear('testserver.local', '/', 'IDP_SESSION')
assert 'IDP_SESSION' not in app.cookies
# verify MELLON_PASSIVE_TRIED is removed
assert 'MELLON_PASSIVE_TRIED' in app.cookies
response = app.get('/', status=200)
assert 'MELLON_PASSIVE_TRIED' not in app.cookies
# check passive authentication is tried again
app.set_cookie(str('IDP_SESSION'), str('1'))
response = app.get('/', status=302)
assert urlparse.urlparse(response.location).path == '/login/'
assert (urlparse.parse_qs(urlparse.urlparse(response.location).query, keep_blank_values=True)
== {'next': ['http://testserver/'], 'passive': ['']})
assert 'MELLON_PASSIVE_TRIED' in app.cookies

View File

@ -13,8 +13,6 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import django
from django.conf.urls import url, include from django.conf.urls import url, include
from django.http import HttpResponse from django.http import HttpResponse

View File

@ -21,12 +21,14 @@ if hasattr(global_settings, 'MIDDLEWARE_CLASSES'):
MIDDLEWARE_CLASSES += ( MIDDLEWARE_CLASSES += (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'mellon.middleware.PassiveAuthenticationMiddleware',
) )
else: else:
MIDDLEWARE = global_settings.MIDDLEWARE MIDDLEWARE = global_settings.MIDDLEWARE
MIDDLEWARE += ( MIDDLEWARE += (
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'mellon.middleware.PassiveAuthenticationMiddleware',
) )
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (