Determine value for op_browser_state from session_key or default
This commit is contained in:
parent
748a8bdfb8
commit
62a0a48678
6 changed files with 102 additions and 10 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
from hashlib import sha224
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
@ -50,6 +52,7 @@ def get_site_url(site_url=None, request=None):
|
||||||
'or set `SITE_URL` in settings, '
|
'or set `SITE_URL` in settings, '
|
||||||
'or pass `request` object.')
|
'or pass `request` object.')
|
||||||
|
|
||||||
|
|
||||||
def get_issuer(site_url=None, request=None):
|
def get_issuer(site_url=None, request=None):
|
||||||
"""
|
"""
|
||||||
Construct the issuer full url. Basically is the site url with some path
|
Construct the issuer full url. Basically is the site url with some path
|
||||||
|
@ -125,3 +128,11 @@ def default_idtoken_processing_hook(id_token, user):
|
||||||
:rtype dict
|
:rtype dict
|
||||||
"""
|
"""
|
||||||
return id_token
|
return id_token
|
||||||
|
|
||||||
|
|
||||||
|
def get_browser_state_or_default(request):
|
||||||
|
"""
|
||||||
|
Determine value to use as session state.
|
||||||
|
"""
|
||||||
|
key = request.session.session_key or settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY')
|
||||||
|
return sha224(key.encode('utf-8')).hexdigest()
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
from hashlib import sha224
|
|
||||||
|
|
||||||
from django.conf import settings as django_settings
|
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
|
from oidc_provider import settings
|
||||||
|
from oidc_provider.lib.utils.common import get_browser_state_or_default
|
||||||
|
|
||||||
|
|
||||||
class SessionManagementMiddleware(MiddlewareMixin):
|
class SessionManagementMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
Maintain a `op_browser_state` cookie along with the `sessionid` cookie that
|
Maintain a `op_browser_state` cookie along with the `sessionid` cookie that
|
||||||
represents the End-User's login state at the OP. If the user is not logged
|
represents the End-User's login state at the OP. If the user is not logged
|
||||||
in then use `SECRET_KEY` value.
|
in then use the value of settings.OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
session_state = sha224((request.session.session_key or django_settings.SECRET_KEY).encode('utf-8')).hexdigest()
|
if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
|
||||||
response.set_cookie('op_browser_state', session_state)
|
response.set_cookie('op_browser_state', get_browser_state_or_default(request))
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import importlib
|
import importlib
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -6,6 +8,9 @@ from django.conf import settings
|
||||||
class DefaultSettings(object):
|
class DefaultSettings(object):
|
||||||
required_attrs = ()
|
required_attrs = ()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._unauthenticated_session_management_key = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def OIDC_LOGIN_URL(self):
|
def OIDC_LOGIN_URL(self):
|
||||||
"""
|
"""
|
||||||
|
@ -74,6 +79,18 @@ class DefaultSettings(object):
|
||||||
"""
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY(self):
|
||||||
|
"""
|
||||||
|
OPTIONAL. Supply a fixed string to use as browser-state key for unauthenticated clients.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Memoize generated value
|
||||||
|
if not self._unauthenticated_session_management_key:
|
||||||
|
self._unauthenticated_session_management_key = ''.join(
|
||||||
|
random.choice(string.ascii_uppercase + string.digits) for _ in range(100))
|
||||||
|
return self._unauthenticated_session_management_key
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def OIDC_SKIP_CONSENT_ALWAYS(self):
|
def OIDC_SKIP_CONSENT_ALWAYS(self):
|
||||||
"""
|
"""
|
||||||
|
|
36
oidc_provider/tests/test_middleware.py
Normal file
36
oidc_provider/tests/test_middleware.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
|
from django.views import View
|
||||||
|
from mock import mock
|
||||||
|
|
||||||
|
|
||||||
|
class StubbedViews:
|
||||||
|
class SampleView(View):
|
||||||
|
pass
|
||||||
|
|
||||||
|
urlpatterns = [url('^test/', SampleView.as_view())]
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF=StubbedViews,
|
||||||
|
MIDDLEWARE=('django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'oidc_provider.middleware.SessionManagementMiddleware'),
|
||||||
|
OIDC_SESSION_MANAGEMENT_ENABLE=True)
|
||||||
|
class MiddlewareTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
patcher = mock.patch('oidc_provider.middleware.get_browser_state_or_default')
|
||||||
|
self.mock_get_state = patcher.start()
|
||||||
|
|
||||||
|
def test_session_management_middleware_sets_cookie_on_response(self):
|
||||||
|
response = self.client.get('/test/')
|
||||||
|
|
||||||
|
self.assertIn('op_browser_state', response.cookies)
|
||||||
|
self.assertEqual(response.cookies['op_browser_state'].value,
|
||||||
|
str(self.mock_get_state.return_value))
|
||||||
|
self.mock_get_state.assert_called_once_with(response.wsgi_request)
|
||||||
|
|
||||||
|
@override_settings(OIDC_SESSION_MANAGEMENT_ENABLE=False)
|
||||||
|
def test_session_management_middleware_does_not_set_cookie_if_session_management_disabled(self):
|
||||||
|
response = self.client.get('/test/')
|
||||||
|
|
||||||
|
self.assertNotIn('op_browser_state', response.cookies)
|
|
@ -8,8 +8,18 @@ CUSTOM_TEMPLATES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TokenTest(TestCase):
|
class SettingsTest(TestCase):
|
||||||
|
|
||||||
@override_settings(OIDC_TEMPLATES=CUSTOM_TEMPLATES)
|
@override_settings(OIDC_TEMPLATES=CUSTOM_TEMPLATES)
|
||||||
def test_override_templates(self):
|
def test_override_templates(self):
|
||||||
self.assertEqual(settings.get('OIDC_TEMPLATES'), CUSTOM_TEMPLATES)
|
self.assertEqual(settings.get('OIDC_TEMPLATES'), CUSTOM_TEMPLATES)
|
||||||
|
|
||||||
|
def test_unauthenticated_session_management_key_has_default(self):
|
||||||
|
key = settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY')
|
||||||
|
self.assertRegexpMatches(key, r'[a-zA-Z0-9]+')
|
||||||
|
self.assertGreater(len(key), 50)
|
||||||
|
|
||||||
|
def test_unauthenticated_session_management_key_has_constant_value(self):
|
||||||
|
key1 = settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY')
|
||||||
|
key2 = settings.get('OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY')
|
||||||
|
self.assertEqual(key1, key2)
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from hashlib import sha224
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.http import HttpRequest
|
||||||
|
from django.test import TestCase, override_settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from mock import mock
|
||||||
|
|
||||||
from oidc_provider.lib.utils.common import get_issuer
|
from oidc_provider.lib.utils.common import get_issuer, get_browser_state_or_default
|
||||||
from oidc_provider.lib.utils.token import create_id_token
|
from oidc_provider.lib.utils.token import create_id_token
|
||||||
from oidc_provider.tests.app.utils import create_fake_user
|
from oidc_provider.tests.app.utils import create_fake_user
|
||||||
from django.test import override_settings
|
|
||||||
|
|
||||||
|
|
||||||
class Request(object):
|
class Request(object):
|
||||||
|
@ -78,3 +80,19 @@ class TokenTest(TestCase):
|
||||||
'iss': 'http://localhost:8000/openid',
|
'iss': 'http://localhost:8000/openid',
|
||||||
'sub': str(self.user.id),
|
'sub': str(self.user.id),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserStateTest(TestCase):
|
||||||
|
|
||||||
|
@override_settings(OIDC_UNAUTHENTICATED_SESSION_MANAGEMENT_KEY='my_static_key')
|
||||||
|
def test_get_browser_state_uses_value_from_settings_to_calculate_browser_state(self):
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = mock.Mock(session_key=None)
|
||||||
|
state = get_browser_state_or_default(request)
|
||||||
|
self.assertEqual(state, sha224('my_static_key'.encode('utf-8')).hexdigest())
|
||||||
|
|
||||||
|
def test_get_browser_state_uses_session_key_to_calculate_browser_state_if_available(self):
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = mock.Mock(session_key='my_session_key')
|
||||||
|
state = get_browser_state_or_default(request)
|
||||||
|
self.assertEqual(state, sha224('my_session_key'.encode('utf-8')).hexdigest())
|
||||||
|
|
Loading…
Reference in a new issue