summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2022-03-03 18:57:00 +0000
committerGerrit Code Review <review@openstack.org>2022-03-03 18:57:00 +0000
commitba079cbe38093dc38b318cb32eef7ca484a24157 (patch)
treed96a756ab9fbbd7b45234e459bebb912f98d78db
parentdd9e4c82b552476ceb003147ac094831a3c3138f (diff)
parent5d6eefa498cf532bde6e9aed4ed278597a8289bc (diff)
downloadhorizon-ba079cbe38093dc38b318cb32eef7ca484a24157.tar.gz
Merge "Create Horizon session control logic"
-rw-r--r--doc/source/configuration/settings.rst16
-rw-r--r--horizon/defaults.py3
-rw-r--r--horizon/middleware/__init__.py2
-rw-r--r--horizon/middleware/simultaneous_sessions.py50
-rw-r--r--horizon/test/unit/middleware/test_simultaneous_sessions.py61
-rw-r--r--openstack_dashboard/settings.py1
-rw-r--r--releasenotes/notes/bp-handle-multiple-login-sessions-from-same-user-in-horizon-448baa6534a8a451.yaml11
7 files changed, 144 insertions, 0 deletions
diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst
index a24e5b287..0d577c1bc 100644
--- a/doc/source/configuration/settings.rst
+++ b/doc/source/configuration/settings.rst
@@ -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
--------------------
diff --git a/horizon/defaults.py b/horizon/defaults.py
index 0e4c9176b..dcef6dd07 100644
--- a/horizon/defaults.py
+++ b/horizon/defaults.py
@@ -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',
diff --git a/horizon/middleware/__init__.py b/horizon/middleware/__init__.py
index 6f8067263..1f00a294f 100644
--- a/horizon/middleware/__init__.py
+++ b/horizon/middleware/__init__.py
@@ -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
diff --git a/horizon/middleware/simultaneous_sessions.py b/horizon/middleware/simultaneous_sessions.py
new file mode 100644
index 000000000..5ee217ab4
--- /dev/null
+++ b/horizon/middleware/simultaneous_sessions.py
@@ -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)
diff --git a/horizon/test/unit/middleware/test_simultaneous_sessions.py b/horizon/test/unit/middleware/test_simultaneous_sessions.py
new file mode 100644
index 000000000..371f54f73
--- /dev/null
+++ b/horizon/test/unit/middleware/test_simultaneous_sessions.py
@@ -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()
diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py
index 93f6f03d0..b192efadf 100644
--- a/openstack_dashboard/settings.py
+++ b/openstack_dashboard/settings.py
@@ -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',
diff --git a/releasenotes/notes/bp-handle-multiple-login-sessions-from-same-user-in-horizon-448baa6534a8a451.yaml b/releasenotes/notes/bp-handle-multiple-login-sessions-from-same-user-in-horizon-448baa6534a8a451.yaml
new file mode 100644
index 000000000..513e51460
--- /dev/null
+++ b/releasenotes/notes/bp-handle-multiple-login-sessions-from-same-user-in-horizon-448baa6534a8a451.yaml
@@ -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.