You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by dp...@apache.org on 2023/10/31 16:05:26 UTC

(superset) branch master updated: feat: support server-side sessions (#25795)

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

dpgaspar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new d2f511abba feat: support server-side sessions (#25795)
d2f511abba is described below

commit d2f511abba5240c137405267e0ebe30b9e3504d4
Author: Daniel Vaz Gaspar <da...@gmail.com>
AuthorDate: Tue Oct 31 16:05:18 2023 +0000

    feat: support server-side sessions (#25795)
---
 docs/docs/security/security.mdx     | 34 ++++++++++++++++++++++++++++++++--
 requirements/base.txt               |  7 ++++++-
 requirements/testing.txt            |  3 ++-
 setup.py                            |  1 +
 superset/config.py                  | 12 ++++++++++++
 superset/initialization/__init__.py |  6 ++++++
 6 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/docs/docs/security/security.mdx b/docs/docs/security/security.mdx
index b92890272e..b3a4ae09c5 100644
--- a/docs/docs/security/security.mdx
+++ b/docs/docs/security/security.mdx
@@ -4,7 +4,7 @@ hide_title: true
 sidebar_position: 1
 ---
 
-Security in Superset is handled by Flask AppBuilder (FAB), an application development framework
+Authentication and authorization in Superset is handled by Flask AppBuilder (FAB), an application development framework
 built on top of Flask. FAB provides authentication, user management, permissions and roles.
 Please read its [Security documentation](https://flask-appbuilder.readthedocs.io/en/latest/security.html).
 
@@ -67,7 +67,9 @@ objects (dashboards and slices) associated with the tables you just extended the
 
 ### REST API for user & role management
 
-Flask-AppBuilder supports a REST API for user CRUD, but this feature is in beta and is not enabled by default in Superset.  To enable this feature, set the following in your Superset configuration:
+Flask-AppBuilder supports a REST API for user CRUD,
+but this feature is in beta and is not enabled by default in Superset.
+To enable this feature, set the following in your Superset configuration:
 
 ```python
 FAB_ADD_SECURITY_API = True
@@ -165,6 +167,34 @@ HTTPS if the cookie is marked “secure”. The application must be served over
 
 `PERMANENT_SESSION_LIFETIME`: (default: "31 days") The lifetime of a permanent session as a `datetime.timedelta` object.
 
+#### Switching to server side sessions
+
+Server side sessions offer benefits over client side sessions on security and performance.
+By enabling server side sessions, the session data is stored server side and only a session ID
+is sent to the client. When a user logs in, a session is created server side and the session ID
+is sent to the client in a cookie. The client will send the session ID with each request and the
+server will use it to retrieve the session data.
+On logout, the session is destroyed server side and the session cookie is deleted on the client side.
+This reduces the risk for replay attacks and session hijacking.
+
+Superset uses [Flask-Session](https://flask-session.readthedocs.io/en/latest/) to manage server side sessions.
+To enable this extension you have to set:
+
+``` python
+SESSION_SERVER_SIDE = True
+```
+
+Flask-Session offers multiple backend session interfaces for Flask, here's an example for Redis:
+
+``` python
+from redis import Redis
+
+SESSION_TYPE = "redis"
+SESSION_REDIS = Redis(host="redis", port=6379, db=0)
+# sign the session cookie sid
+SESSION_USE_SIGNER = True
+```
+
 ### Content Security Policy (CSP)
 
 Superset uses the [Talisman](https://pypi.org/project/flask-talisman/) extension to enable implementation of a
diff --git a/requirements/base.txt b/requirements/base.txt
index 5734f68d71..d056b403c3 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -32,7 +32,9 @@ bottleneck==1.3.7
 brotli==1.0.9
     # via flask-compress
 cachelib==0.6.0
-    # via flask-caching
+    # via
+    #   flask-caching
+    #   flask-session
 celery==5.2.2
     # via apache-superset
 certifi==2023.7.22
@@ -94,6 +96,7 @@ flask==2.2.5
     #   flask-limiter
     #   flask-login
     #   flask-migrate
+    #   flask-session
     #   flask-sqlalchemy
     #   flask-wtf
 flask-appbuilder==4.3.9
@@ -114,6 +117,8 @@ flask-login==0.6.0
     #   flask-appbuilder
 flask-migrate==3.1.0
     # via apache-superset
+flask-session==0.5.0
+    # via apache-superset
 flask-sqlalchemy==2.5.1
     # via
     #   flask-appbuilder
diff --git a/requirements/testing.txt b/requirements/testing.txt
index d3cfde5218..00fe734540 100644
--- a/requirements/testing.txt
+++ b/requirements/testing.txt
@@ -48,7 +48,6 @@ google-auth==2.17.3
     #   google-cloud-core
     #   pandas-gbq
     #   pydata-google-auth
-    #   shillelagh
     #   sqlalchemy-bigquery
 google-auth-oauthlib==1.0.0
     # via
@@ -120,6 +119,8 @@ protobuf==4.23.0
     #   proto-plus
 pydata-google-auth==1.7.0
     # via pandas-gbq
+pyee==9.0.4
+    # via playwright
 pyfakefs==5.2.2
     # via -r requirements/testing.in
 pyhive[presto]==0.7.0
diff --git a/setup.py b/setup.py
index a5bd4347ff..2ea20c29ac 100644
--- a/setup.py
+++ b/setup.py
@@ -90,6 +90,7 @@ setup(
         "flask-talisman>=1.0.0, <2.0",
         "flask-login>=0.6.0, < 1.0",
         "flask-migrate>=3.1.0, <4.0",
+        "flask-session>=0.4.0, <1.0",
         "flask-wtf>=1.1.0, <2.0",
         "func_timeout",
         "geopy",
diff --git a/superset/config.py b/superset/config.py
index dd244dc14a..a85cbe82e0 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -1482,6 +1482,18 @@ TALISMAN_DEV_CONFIG = {
 SESSION_COOKIE_HTTPONLY = True  # Prevent cookie from being read by frontend JS?
 SESSION_COOKIE_SECURE = False  # Prevent cookie from being transmitted over non-tls?
 SESSION_COOKIE_SAMESITE: Literal["None", "Lax", "Strict"] | None = "Lax"
+# Whether to use server side sessions from flask-session or Flask secure cookies
+SESSION_SERVER_SIDE = False
+# Example config using Redis as the backend for server side sessions
+# from flask_session import RedisSessionInterface
+#
+# SESSION_SERVER_SIDE = True
+# SESSION_USE_SIGNER = True
+# SESSION_TYPE = "redis"
+# SESSION_REDIS = Redis(host="localhost", port=6379, db=0)
+#
+# Other possible config options and backends:
+# # https://flask-session.readthedocs.io/en/latest/config.html
 
 # Cache static resources.
 SEND_FILE_MAX_AGE_DEFAULT = int(timedelta(days=365).total_seconds())
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index e84689994a..09212120ec 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -28,6 +28,7 @@ from flask import Flask, redirect
 from flask_appbuilder import expose, IndexView
 from flask_babel import gettext as __
 from flask_compress import Compress
+from flask_session import Session
 from werkzeug.middleware.proxy_fix import ProxyFix
 
 from superset.constants import CHANGE_ME_SECRET_KEY
@@ -479,6 +480,10 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
             logger.error("Refusing to start due to insecure SECRET_KEY")
             sys.exit(1)
 
+    def configure_session(self) -> None:
+        if self.config["SESSION_SERVER_SIDE"]:
+            Session(self.superset_app)
+
     def init_app(self) -> None:
         """
         Main entry point which will delegate to other methods in
@@ -486,6 +491,7 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
         """
         self.pre_init()
         self.check_secret_key()
+        self.configure_session()
         # Configuration of logging must be done first to apply the formatter properly
         self.configure_logging()
         # Configuration of feature_flags must be done first to allow init features