This is an automated email from the ASF dual-hosted git repository.

jedcunningham pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 9a034e678b Rotate session id during login (#25771)
9a034e678b is described below

commit 9a034e678bc6b5ef771b803d0186119b6fb82e67
Author: Jed Cunningham <[email protected]>
AuthorDate: Thu Aug 18 06:52:02 2022 -0700

    Rotate session id during login (#25771)
---
 airflow/www/fab_security/manager.py | 15 +++++++++++++++
 tests/www/views/test_session.py     | 28 ++++++++++++++++++++++++++--
 2 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/airflow/www/fab_security/manager.py 
b/airflow/www/fab_security/manager.py
index 8399f11df3..3317fb014b 100644
--- a/airflow/www/fab_security/manager.py
+++ b/airflow/www/fab_security/manager.py
@@ -25,6 +25,7 @@ import json
 import logging
 import re
 from typing import Any, Dict, List, Optional, Set, Tuple, Type
+from uuid import uuid4
 
 from flask import current_app, g, session, url_for
 from flask_appbuilder import AppBuilder
@@ -70,6 +71,7 @@ from flask_jwt_extended import JWTManager, current_user as 
current_user_jwt
 from flask_login import AnonymousUserMixin, LoginManager, current_user
 from werkzeug.security import check_password_hash, generate_password_hash
 
+from airflow.configuration import conf
 from airflow.www.fab_security.sqla.models import Action, Permission, 
RegisterUser, Resource, Role, User
 from airflow.www.views import ResourceModelView
 
@@ -854,6 +856,14 @@ class BaseSecurityManager:
             user.fail_login_count += 1
         self.update_user(user)
 
+    def _rotate_session_id(self):
+        """
+        Upon successful authentication when using the database session backend,
+        we need to rotate the session id
+        """
+        if conf.get('webserver', 'SESSION_BACKEND') == 'database':
+            session.sid = str(uuid4())
+
     def auth_user_db(self, username, password):
         """
         Method for authenticating user, auth db style
@@ -878,6 +888,7 @@ class BaseSecurityManager:
             log.info(LOGMSG_WAR_SEC_LOGIN_FAILED.format(username))
             return None
         elif check_password_hash(user.password, password):
+            self._rotate_session_id()
             self.update_user_auth_stat(user, True)
             return user
         else:
@@ -1174,6 +1185,7 @@ class BaseSecurityManager:
 
             # LOGIN SUCCESS (only if user is now registered)
             if user:
+                self._rotate_session_id()
                 self.update_user_auth_stat(user)
                 return user
             else:
@@ -1201,6 +1213,7 @@ class BaseSecurityManager:
             log.info(LOGMSG_WAR_SEC_LOGIN_FAILED.format(email))
             return None
         else:
+            self._rotate_session_id()
             self.update_user_auth_stat(user)
             return user
 
@@ -1230,6 +1243,7 @@ class BaseSecurityManager:
             log.info(LOGMSG_WAR_SEC_LOGIN_FAILED.format(username))
             return None
 
+        self._rotate_session_id()
         self.update_user_auth_stat(user)
         return user
 
@@ -1315,6 +1329,7 @@ class BaseSecurityManager:
 
         # LOGIN SUCCESS (only if user is now registered)
         if user:
+            self._rotate_session_id()
             self.update_user_auth_stat(user)
             return user
         else:
diff --git a/tests/www/views/test_session.py b/tests/www/views/test_session.py
index 9fb6f36469..0753f2069a 100644
--- a/tests/www/views/test_session.py
+++ b/tests/www/views/test_session.py
@@ -15,6 +15,8 @@
 # specific language governing permissions and limitations
 # under the License.
 
+from unittest import mock
+
 import pytest
 
 from airflow.exceptions import AirflowConfigException
@@ -23,12 +25,16 @@ from tests.test_utils.config import conf_vars
 from tests.test_utils.decorators import dont_initialize_flask_app_submodules
 
 
+def get_session_cookie(client):
+    return next((cookie for cookie in client.cookie_jar if cookie.name == 
'session'), None)
+
+
 def test_session_cookie_created_on_login(user_client):
-    assert any(cookie.name == 'session' for cookie in user_client.cookie_jar)
+    assert get_session_cookie(user_client) is not None
 
 
 def test_session_inaccessible_after_logout(user_client):
-    session_cookie = next((cookie for cookie in user_client.cookie_jar if 
cookie.name == 'session'), None)
+    session_cookie = get_session_cookie(user_client)
     assert session_cookie is not None
 
     resp = user_client.get('/logout/')
@@ -63,3 +69,21 @@ def test_invalid_session_backend_option():
     )
     with pytest.raises(AirflowConfigException, match=expected_exc_regex):
         poorly_configured_app_factory()
+
+
+def test_session_id_rotates(app, user_client):
+    old_session_cookie = get_session_cookie(user_client)
+    assert old_session_cookie is not None
+
+    resp = user_client.get('/logout/')
+    assert resp.status_code == 302
+
+    patch_path = "airflow.www.fab_security.manager.check_password_hash"
+    with mock.patch(patch_path) as check_password_hash:
+        check_password_hash.return_value = True
+        resp = user_client.post("/login/", data={"username": "test_user", 
"password": "test_user"})
+    assert resp.status_code == 302
+
+    new_session_cookie = get_session_cookie(user_client)
+    assert new_session_cookie is not None
+    assert old_session_cookie.value != new_session_cookie.value

Reply via email to