kopia lustrzana https://github.com/simonw/datasette
skip_csrf(datasette, scope) plugin hook, refs #1377
rodzic
4a3e8561ab
commit
b1fd24ac9f
|
@ -1052,6 +1052,9 @@ class Datasette:
|
|||
DatasetteRouter(self, routes),
|
||||
signing_secret=self._secret,
|
||||
cookie_name="ds_csrftoken",
|
||||
skip_if_scope=lambda scope: any(
|
||||
pm.hook.skip_csrf(datasette=self, scope=scope)
|
||||
),
|
||||
)
|
||||
if self.setting("trace_debug"):
|
||||
asgi = AsgiTracer(asgi)
|
||||
|
|
|
@ -112,3 +112,8 @@ def table_actions(datasette, actor, database, table, request):
|
|||
@hookspec
|
||||
def database_actions(datasette, actor, database, request):
|
||||
"""Links for the database actions menu"""
|
||||
|
||||
|
||||
@hookspec
|
||||
def skip_csrf(datasette, scope):
|
||||
"""Mechanism for skipping CSRF checks for certain requests"""
|
||||
|
|
|
@ -778,6 +778,8 @@ If your plugin implements a ``<form method="POST">`` anywhere you will need to i
|
|||
|
||||
<input type="hidden" name="csrftoken" value="{{ csrftoken() }}">
|
||||
|
||||
You can selectively disable CSRF protection using the :ref:`plugin_hook_skip_csrf` hook.
|
||||
|
||||
.. _internals_internal:
|
||||
|
||||
The _internal database
|
||||
|
|
|
@ -1104,3 +1104,28 @@ database_actions(datasette, actor, database, request)
|
|||
The current HTTP :ref:`internals_request`.
|
||||
|
||||
This hook is similar to :ref:`plugin_hook_table_actions` but populates an actions menu on the database page.
|
||||
|
||||
.. _plugin_hook_skip_csrf:
|
||||
|
||||
skip_csrf(datasette, scope)
|
||||
---------------------------
|
||||
|
||||
``datasette`` - :ref:`internals_datasette`
|
||||
You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``, or to execute SQL queries.
|
||||
|
||||
``scope`` - dictionary
|
||||
The `ASGI scope <https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope>`__ for the incoming HTTP request.
|
||||
|
||||
This hook can be used to skip :ref:`internals_csrf` for a specific incoming request. For example, you might have a custom path at ``/submit-comment`` which is designed to accept comments from anywhere, whether or not the incoming request originated on the site and has an accompanying CSRF token.
|
||||
|
||||
This example will disable CSRF protection for that specific URL path:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette import hookimpl
|
||||
|
||||
@hookimpl
|
||||
def skip_csrf(scope):
|
||||
return scope["path"] == "/submit-comment"
|
||||
|
||||
If any of the currently active ``skip_csrf()`` plugin hooks return ``True``, CSRF protection will be skipped for the request.
|
||||
|
|
2
setup.py
2
setup.py
|
@ -55,7 +55,7 @@ setup(
|
|||
"uvicorn~=0.11",
|
||||
"aiofiles>=0.4,<0.8",
|
||||
"janus>=0.4,<0.7",
|
||||
"asgi-csrf>=0.6",
|
||||
"asgi-csrf>=0.9",
|
||||
"PyYAML~=5.3",
|
||||
"mergedeep>=1.1.1,<1.4.0",
|
||||
"itsdangerous>=1.1,<3.0",
|
||||
|
|
|
@ -52,6 +52,7 @@ EXPECTED_PLUGINS = [
|
|||
"register_magic_parameters",
|
||||
"register_routes",
|
||||
"render_cell",
|
||||
"skip_csrf",
|
||||
"startup",
|
||||
"table_actions",
|
||||
],
|
||||
|
@ -152,6 +153,7 @@ def make_app_client(
|
|||
static_mounts=static_mounts,
|
||||
template_dir=template_dir,
|
||||
crossdb=crossdb,
|
||||
pdb=True,
|
||||
)
|
||||
ds.sqlite_functions.append(("sleep", 1, lambda n: time.sleep(float(n))))
|
||||
yield TestClient(ds)
|
||||
|
|
|
@ -348,3 +348,8 @@ def database_actions(datasette, database, actor, request):
|
|||
"label": label,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@hookimpl
|
||||
def skip_csrf(scope):
|
||||
return scope["path"] == "/skip-csrf"
|
||||
|
|
|
@ -825,3 +825,28 @@ def test_hook_database_actions(app_client):
|
|||
assert get_table_actions_links(response_2.text) == [
|
||||
{"label": "Database: fixtures - BOB", "href": "/"},
|
||||
]
|
||||
|
||||
|
||||
def test_hook_skip_csrf(app_client):
|
||||
cookie = app_client.actor_cookie({"id": "test"})
|
||||
csrf_response = app_client.post(
|
||||
"/post/",
|
||||
post_data={"this is": "post data"},
|
||||
csrftoken_from=True,
|
||||
cookies={"ds_actor": cookie},
|
||||
)
|
||||
assert csrf_response.status == 200
|
||||
missing_csrf_response = app_client.post(
|
||||
"/post/", post_data={"this is": "post data"}, cookies={"ds_actor": cookie}
|
||||
)
|
||||
assert missing_csrf_response.status == 403
|
||||
# But "/skip-csrf" should allow
|
||||
allow_csrf_response = app_client.post(
|
||||
"/skip-csrf", post_data={"this is": "post data"}, cookies={"ds_actor": cookie}
|
||||
)
|
||||
assert allow_csrf_response.status == 405 # Method not allowed
|
||||
# /skip-csrf-2 should not
|
||||
second_missing_csrf_response = app_client.post(
|
||||
"/skip-csrf-2", post_data={"this is": "post data"}, cookies={"ds_actor": cookie}
|
||||
)
|
||||
assert second_missing_csrf_response.status == 403
|
||||
|
|
Ładowanie…
Reference in New Issue