Merge "Create Horizon session control logic"
This commit is contained in:
commit
ba079cbe38
@ -953,6 +953,22 @@ menu and the api access panel.
|
||||
`OPENSTACK_CLOUDS_YAML_CUSTOM_TEMPLATE`_ to provide a custom
|
||||
``clouds.yaml``.
|
||||
|
||||
SIMULTANEOUS_SESSIONS
|
||||
---------------------
|
||||
|
||||
.. versionadded:: 21.1.0(Yoga)
|
||||
|
||||
Default: ``allow``
|
||||
|
||||
Controls whether a user can have multiple simultaneous sessions.
|
||||
Valid values are ``allow`` and ``disconnect``.
|
||||
|
||||
The value ``allow`` enables more than one simultaneous sessions for a user.
|
||||
The Value ``disconnect`` disables more than one simultaneous sessions for
|
||||
a user. Only one active session is allowed. The newer session will be
|
||||
considered as the valid one and any existing session will be disconnected
|
||||
after a subsequent successful login.
|
||||
|
||||
THEME_COLLECTION_DIR
|
||||
--------------------
|
||||
|
||||
|
@ -86,6 +86,9 @@ OPERATION_LOG_OPTIONS = {
|
||||
),
|
||||
}
|
||||
|
||||
# Control whether a same user can have multiple action sessions.
|
||||
SIMULTANEOUS_SESSIONS = 'allow'
|
||||
|
||||
OPENSTACK_PROFILER = {
|
||||
'enabled': False,
|
||||
'facility_name': 'horizon',
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
from horizon.middleware import base
|
||||
from horizon.middleware import operation_log
|
||||
from horizon.middleware import simultaneous_sessions as sessions
|
||||
|
||||
HorizonMiddleware = base.HorizonMiddleware
|
||||
OperationLogMiddleware = operation_log.OperationLogMiddleware
|
||||
SimultaneousSessionsMiddleware = sessions.SimultaneousSessionsMiddleware
|
||||
|
50
horizon/middleware/simultaneous_sessions.py
Normal file
50
horizon/middleware/simultaneous_sessions.py
Normal file
@ -0,0 +1,50 @@
|
||||
# Copyright (c) 2021 Wind River Systems Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import importlib
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SimultaneousSessionsMiddleware(object):
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
self.simultaneous_sessions = settings.SIMULTANEOUS_SESSIONS
|
||||
|
||||
def __call__(self, request):
|
||||
self._process_request(request)
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
||||
def _process_request(self, request):
|
||||
cache = caches['default']
|
||||
cache_key = ('user_pk_{}_restrict').format(request.user.pk)
|
||||
cache_value = cache.get(cache_key)
|
||||
if cache_value and self.simultaneous_sessions == 'disconnect':
|
||||
if request.session.session_key != cache_value:
|
||||
LOG.info('The user %s is already logged in, '
|
||||
'the last session will be disconnected.',
|
||||
request.user.id)
|
||||
engine = importlib.import_module(settings.SESSION_ENGINE)
|
||||
session = engine.SessionStore(session_key=cache_value)
|
||||
session.delete()
|
||||
cache.set(cache_key, request.session.session_key,
|
||||
settings.SESSION_TIMEOUT)
|
||||
else:
|
||||
cache.set(cache_key, request.session.session_key,
|
||||
settings.SESSION_TIMEOUT)
|
61
horizon/test/unit/middleware/test_simultaneous_sessions.py
Normal file
61
horizon/test/unit/middleware/test_simultaneous_sessions.py
Normal file
@ -0,0 +1,61 @@
|
||||
# Copyright (c) 2021 Wind River Systems Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sessions.backends import signed_cookies
|
||||
from django import test as django_test
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from horizon import middleware
|
||||
from horizon.test import helpers as test
|
||||
|
||||
|
||||
class SimultaneousSessionsMiddlewareTest(django_test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.url = settings.LOGIN_URL
|
||||
self.factory = test.RequestFactoryWithMessages()
|
||||
self.get_response = mock.Mock()
|
||||
self.request = self.factory.get(self.url)
|
||||
self.request.user.pk = '123'
|
||||
super().setUp()
|
||||
|
||||
@mock.patch.object(signed_cookies.SessionStore, 'delete', return_value=None)
|
||||
def test_simultaneous_sessions(self, mock_delete):
|
||||
mw = middleware.SimultaneousSessionsMiddleware(
|
||||
self.get_response)
|
||||
|
||||
self.request.session._set_session_key('123456789')
|
||||
mw._process_request(self.request)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
self.request.session._set_session_key('987654321')
|
||||
mw._process_request(self.request)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
@override_settings(SIMULTANEOUS_SESSIONS='disconnect')
|
||||
@mock.patch.object(signed_cookies.SessionStore, 'delete', return_value=None)
|
||||
def test_disconnect_simultaneous_sessions(self, mock_delete):
|
||||
mw = middleware.SimultaneousSessionsMiddleware(
|
||||
self.get_response)
|
||||
|
||||
self.request.session._set_session_key('123456789')
|
||||
mw._process_request(self.request)
|
||||
mock_delete.assert_not_called()
|
||||
|
||||
self.request.session._set_session_key('987654321')
|
||||
mw._process_request(self.request)
|
||||
mock_delete.assert_called_once_with()
|
@ -82,6 +82,7 @@ MIDDLEWARE = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'horizon.middleware.OperationLogMiddleware',
|
||||
'horizon.middleware.SimultaneousSessionsMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'horizon.middleware.HorizonMiddleware',
|
||||
'horizon.themes.ThemeMiddleware',
|
||||
|
@ -0,0 +1,11 @@
|
||||
features:
|
||||
- |
|
||||
[:blueprint:`handle-multiple-login-sessions-from-same-user-in-horizon`]
|
||||
This blueprint allows operators to control if multiple simultaneous
|
||||
dashboard sessions are allowed or not for a user. A new setting
|
||||
``SIMULTANEOUS_SESSIONS`` controls the behavior. The default behavior
|
||||
allows multiple dashboard sessions for a user. The new setting allows
|
||||
operators to configure horizon to disallow multiple sessions per user.
|
||||
When multiple simultaneous sessions are disabled, the most recent
|
||||
authenticated session will be considered as the valid one and
|
||||
the previous session will be invalidated.
|
Loading…
Reference in New Issue
Block a user