Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 32805b1

Browse files
authored
Merge pull request #31294 from scottshambaugh/webagg_cross_origin
MNT: Restrict webagg toolbar actions to valid actions
2 parents 8ff895d + 81e0be7 commit 32805b1

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

lib/matplotlib/backends/backend_webagg_core.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,11 @@ def _handle_key(self, event):
349349
handle_key_press = handle_key_release = _handle_key
350350

351351
def handle_toolbar_button(self, event):
352-
# TODO: Be more suspicious of the input
353-
getattr(self.toolbar, event['name'])()
352+
name = event['name']
353+
for item in self.toolbar.toolitems:
354+
if item[3] is not None and name == item[3]:
355+
getattr(self.toolbar, name)()
356+
break
354357

355358
def handle_refresh(self, event):
356359
if self.manager:

lib/matplotlib/tests/test_backend_webagg.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import os
22
import sys
3+
from unittest.mock import MagicMock
4+
35
import pytest
46

57
import matplotlib.backends.backend_webagg_core
8+
from matplotlib.backends.backend_webagg_core import (
9+
FigureCanvasWebAggCore, NavigationToolbar2WebAgg,
10+
)
611
from matplotlib.testing import subprocess_run_for_testing
712

813

@@ -30,3 +35,46 @@ def test_webagg_fallback(backend):
3035
def test_webagg_core_no_toolbar():
3136
fm = matplotlib.backends.backend_webagg_core.FigureManagerWebAgg
3237
assert fm._toolbar2_class is None
38+
39+
40+
def test_toolbar_button_dispatch_allowlist():
41+
"""Only declared toolbar items should be dispatched."""
42+
fig = MagicMock()
43+
canvas = FigureCanvasWebAggCore(fig)
44+
canvas.toolbar = MagicMock(spec=NavigationToolbar2WebAgg)
45+
canvas.toolbar.toolitems = NavigationToolbar2WebAgg.toolitems
46+
47+
# Valid toolbar action should be dispatched.
48+
canvas.handle_toolbar_button({'name': 'home'})
49+
canvas.toolbar.home.assert_called_once()
50+
51+
# Invalid names should be silently ignored.
52+
canvas.toolbar.reset_mock()
53+
canvas.handle_toolbar_button({'name': '__init__'})
54+
canvas.handle_toolbar_button({'name': 'not_a_real_button'})
55+
# No methods should have been called.
56+
assert canvas.toolbar.method_calls == []
57+
58+
59+
@pytest.mark.parametrize("host, origin, allowed", [
60+
("localhost:8988", "http://localhost:8988", True),
61+
("localhost:8988", "http://evil.com", False),
62+
("localhost:8988", "http://127.0.0.1:8988", False),
63+
("localhost:8988", "http://[::1]:8988", False),
64+
("127.0.0.1:8988", "http://127.0.0.1:8988", True),
65+
("127.0.0.1:8988", "http://localhost:8988", False),
66+
("127.0.0.1:8988", "http://[::1]:8988", False),
67+
("[::1]:8988", "http://[::1]:8988", True),
68+
("[::1]:8988", "http://[::2]:8988", False),
69+
("[::1]:8988", "http://localhost:8988", False),
70+
("[::1]:8988", "http://evil.com", False),
71+
])
72+
def test_websocket_rejects_cross_origin(host, origin, allowed):
73+
"""Verify Tornado's default check_origin rejects cross-origin requests."""
74+
pytest.importorskip("tornado")
75+
from matplotlib.backends.backend_webagg import WebAggApplication
76+
77+
ws = WebAggApplication.WebSocket.__new__(WebAggApplication.WebSocket)
78+
ws.request = MagicMock()
79+
ws.request.headers = {"Host": host}
80+
assert ws.check_origin(origin) is allowed

0 commit comments

Comments
 (0)