1414# KIND, either express or implied. See the License for the
1515# specific language governing permissions and limitations
1616# under the License.
17+ import hashlib
1718import logging
18- from typing import Any , Optional , Union
19+ from typing import Any , Callable , Optional , Union
1920
20- from flask import Flask
21+ from flask import current_app , Flask
2122from flask_caching import Cache
2223from markupsafe import Markup
2324
2728
2829CACHE_IMPORT_PATH = "superset.extensions.metastore_cache.SupersetMetastoreCache"
2930
31+ # Hash function lookup table matching superset.utils.hashing
32+ _HASH_METHODS : dict [str , Callable [..., Any ]] = {
33+ "sha256" : hashlib .sha256 ,
34+ "md5" : hashlib .md5 ,
35+ }
36+
37+
38+ class ConfigurableHashMethod :
39+ """
40+ A callable that defers hash algorithm selection to runtime.
41+
42+ Flask-caching's memoize decorator evaluates hash_method at decoration time
43+ (module import), but we need to read HASH_ALGORITHM config at function call
44+ time when the app context is available.
45+
46+ This class acts like a hashlib function but looks up the configured
47+ algorithm when called.
48+ """
49+
50+ def __call__ (self , data : bytes = b"" ) -> Any :
51+ """
52+ Create a hash object using the configured algorithm.
53+
54+ Args:
55+ data: Optional initial data to hash
56+
57+ Returns:
58+ A hashlib hash object (e.g., sha256 or md5)
59+
60+ Raises:
61+ ValueError: If HASH_ALGORITHM is set to an unsupported value
62+ """
63+ algorithm = current_app .config ["HASH_ALGORITHM" ]
64+ hash_func = _HASH_METHODS .get (algorithm )
65+ if hash_func is None :
66+ raise ValueError (f"Unsupported hash algorithm: { algorithm } " )
67+ return hash_func (data )
68+
69+
70+ # Singleton instance to use as default hash_method
71+ configurable_hash_method = ConfigurableHashMethod ()
72+
73+
74+ class SupersetCache (Cache ):
75+ """
76+ Cache subclass that uses the configured HASH_ALGORITHM instead of MD5.
77+
78+ Flask-caching uses MD5 by default for cache key generation, which fails
79+ in FIPS mode where MD5 is disabled. This class overrides the default
80+ hash method to use the algorithm specified by HASH_ALGORITHM config.
81+
82+ Note: Switching hash algorithms will invalidate existing cache keys,
83+ causing a one-time cache miss on upgrade.
84+ """
85+
86+ def memoize (
87+ self ,
88+ timeout : int | None = None ,
89+ make_name : Callable [..., Any ] | None = None ,
90+ unless : Callable [..., bool ] | None = None ,
91+ forced_update : Callable [..., bool ] | None = None ,
92+ response_filter : Callable [..., Any ] | None = None ,
93+ hash_method : Callable [..., Any ] = configurable_hash_method ,
94+ cache_none : bool = False ,
95+ source_check : bool | None = None ,
96+ args_to_ignore : Any | None = None ,
97+ ) -> Callable [..., Any ]:
98+ return super ().memoize (
99+ timeout = timeout ,
100+ make_name = make_name ,
101+ unless = unless ,
102+ forced_update = forced_update ,
103+ response_filter = response_filter ,
104+ hash_method = hash_method ,
105+ cache_none = cache_none ,
106+ source_check = source_check ,
107+ args_to_ignore = args_to_ignore ,
108+ )
109+
110+ def cached (
111+ self ,
112+ timeout : int | None = None ,
113+ key_prefix : str = "view/%s" ,
114+ unless : Callable [..., bool ] | None = None ,
115+ forced_update : Callable [..., bool ] | None = None ,
116+ response_filter : Callable [..., Any ] | None = None ,
117+ query_string : bool = False ,
118+ hash_method : Callable [..., Any ] = configurable_hash_method ,
119+ cache_none : bool = False ,
120+ make_cache_key : Callable [..., Any ] | None = None ,
121+ source_check : bool | None = None ,
122+ response_hit_indication : bool | None = False ,
123+ ) -> Callable [..., Any ]:
124+ return super ().cached (
125+ timeout = timeout ,
126+ key_prefix = key_prefix ,
127+ unless = unless ,
128+ forced_update = forced_update ,
129+ response_filter = response_filter ,
130+ query_string = query_string ,
131+ hash_method = hash_method ,
132+ cache_none = cache_none ,
133+ make_cache_key = make_cache_key ,
134+ source_check = source_check ,
135+ response_hit_indication = response_hit_indication ,
136+ )
137+
138+ # pylint: disable=protected-access
139+ def _memoize_make_cache_key (
140+ self ,
141+ make_name : Callable [..., Any ] | None = None ,
142+ timeout : Callable [..., Any ] | None = None ,
143+ forced_update : bool = False ,
144+ hash_method : Callable [..., Any ] = configurable_hash_method ,
145+ source_check : bool | None = False ,
146+ args_to_ignore : Any | None = None ,
147+ ) -> Callable [..., Any ]:
148+ return super ()._memoize_make_cache_key (
149+ make_name = make_name ,
150+ timeout = timeout ,
151+ forced_update = forced_update ,
152+ hash_method = hash_method ,
153+ source_check = source_check ,
154+ args_to_ignore = args_to_ignore ,
155+ )
156+
30157
31- class ExploreFormDataCache (Cache ):
158+ class ExploreFormDataCache (SupersetCache ):
32159 def get (self , * args : Any , ** kwargs : Any ) -> Optional [Union [str , Markup ]]:
33160 cache = self .cache .get (* args , ** kwargs )
34161
@@ -53,10 +180,10 @@ class CacheManager:
53180 def __init__ (self ) -> None :
54181 super ().__init__ ()
55182
56- self ._cache = Cache ()
57- self ._data_cache = Cache ()
58- self ._thumbnail_cache = Cache ()
59- self ._filter_state_cache = Cache ()
183+ self ._cache = SupersetCache ()
184+ self ._data_cache = SupersetCache ()
185+ self ._thumbnail_cache = SupersetCache ()
186+ self ._filter_state_cache = SupersetCache ()
60187 self ._explore_form_data_cache = ExploreFormDataCache ()
61188
62189 @staticmethod
0 commit comments