33
44import base64
55import copy
6+ import hashlib
7+ import hmac
8+ import os
69import pickle
10+ import secrets
711import uuid
812from collections import namedtuple
913
2933from holoviews .plotting .plotly .util import clean_internal_figure_properties
3034from holoviews .plotting .util import initialize_dynamic
3135from holoviews .streams import Derived , History
36+ from holoviews .util .warnings import deprecated
37+
38+ deprecated ("1.24.0" , "Dash support" , repr_old = False )
39+
3240
3341# Dash imports
3442try :
5058DashComponents = namedtuple ("DashComponents" , ["graphs" , "kdims" , "store" , "resets" , "children" ])
5159HoloViewsFunctionSpec = namedtuple ("HoloViewsFunctionSpec" , ["fn" , "kdims" , "streams" ])
5260
61+ _STORE_SECRET : bytes = os .getenv ("HOLOVIEWS_DASH_TOKEN" , "" ).encode () or secrets .token_bytes (32 )
62+
5363
5464def get_layout_ranges (plot ):
5565 layout_ranges = {}
@@ -272,9 +282,10 @@ def populate_stream_callback_graph(stream_callbacks, streams):
272282def encode_store_data (store_data ):
273283 """Encode store_data dict into a JSON serializable dict
274284
275- This is currently done by pickling store_data and converting to a base64 encoded
276- string. If HoloViews supports JSON serialization in the future, this method could
277- be updated to use this approach instead
285+ The dict is pickled, base64-encoded, and HMAC-signed with a secret
286+ (set via the ``HOLOVIEWS_DASH_TOKEN`` environment variable, or a random
287+ per-process value) so that decode_store_data can reject any payload that
288+ was not produced by a server process sharing the same secret.
278289
279290 Parameters
280291 ----------
@@ -284,21 +295,38 @@ def encode_store_data(store_data):
284295 -------
285296 dict that can be JSON serialized
286297 """
287- return {"pickled" : base64 .b64encode (pickle .dumps (store_data )).decode ("utf-8" )}
298+ pickled = pickle .dumps (store_data )
299+ mac = hmac .new (_STORE_SECRET , pickled , hashlib .sha256 ).hexdigest ()
300+ return {"pickled" : base64 .b64encode (pickled ).decode ("utf-8" ), "mac" : mac }
288301
289302
290303def decode_store_data (store_data ):
291304 """Decode a dict that was encoded by the encode_store_data function.
292305
306+ The HMAC signature (using the secret from ``HOLOVIEWS_DASH_TOKEN`` or the
307+ per-process random value) is verified before unpickling to prevent
308+ deserialisation of attacker-controlled payloads.
309+
293310 Parameters
294311 ----------
295312 store_data : dict that was encoded by encode_store_data
296313
297314 Returns
298315 -------
299316 decoded dict
317+
318+ Raises
319+ ------
320+ ValueError
321+ If the payload signature is missing or does not match.
300322 """
301- return pickle .loads (base64 .b64decode (store_data ["pickled" ]))
323+ pickled = base64 .b64decode (store_data ["pickled" ])
324+ expected_mac = hmac .new (_STORE_SECRET , pickled , hashlib .sha256 ).hexdigest ()
325+ if not hmac .compare_digest (store_data .get ("mac" , "" ), expected_mac ):
326+ raise ValueError (
327+ "Store data integrity check failed, the payload may have been tampered with."
328+ )
329+ return pickle .loads (pickled )
302330
303331
304332def to_dash (
0 commit comments