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
|
`OPENSTACK_CLOUDS_YAML_CUSTOM_TEMPLATE`_ to provide a custom
|
||||||
``clouds.yaml``.
|
``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
|
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 = {
|
OPENSTACK_PROFILER = {
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'facility_name': 'horizon',
|
'facility_name': 'horizon',
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
|
|
||||||
from horizon.middleware import base
|
from horizon.middleware import base
|
||||||
from horizon.middleware import operation_log
|
from horizon.middleware import operation_log
|
||||||
|
from horizon.middleware import simultaneous_sessions as sessions
|
||||||
|
|
||||||
HorizonMiddleware = base.HorizonMiddleware
|
HorizonMiddleware = base.HorizonMiddleware
|
||||||
OperationLogMiddleware = operation_log.OperationLogMiddleware
|
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.sessions.middleware.SessionMiddleware',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'horizon.middleware.OperationLogMiddleware',
|
'horizon.middleware.OperationLogMiddleware',
|
||||||
|
'horizon.middleware.SimultaneousSessionsMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'horizon.middleware.HorizonMiddleware',
|
'horizon.middleware.HorizonMiddleware',
|
||||||
'horizon.themes.ThemeMiddleware',
|
'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