diff --git a/datasette/actor_auth_cookie.py b/datasette/actor_auth_cookie.py
index 41f33fe9..f3a0f306 100644
--- a/datasette/actor_auth_cookie.py
+++ b/datasette/actor_auth_cookie.py
@@ -5,14 +5,9 @@ from http.cookies import SimpleCookie
@hookimpl
def actor_from_request(datasette, request):
- cookies = SimpleCookie()
- cookies.load(
- dict(request.scope.get("headers") or []).get(b"cookie", b"").decode("utf-8")
- )
- if "ds_actor" not in cookies:
+ if "ds_actor" not in request.cookies:
return None
- ds_actor = cookies["ds_actor"].value
try:
- return datasette.unsign(ds_actor, "actor")
+ return datasette.unsign(request.cookies["ds_actor"], "actor")
except BadSignature:
return None
diff --git a/datasette/app.py b/datasette/app.py
index b8a5e23d..633c4a29 100644
--- a/datasette/app.py
+++ b/datasette/app.py
@@ -2,6 +2,7 @@ import asyncio
import collections
import datetime
import hashlib
+from http.cookies import SimpleCookie
import itertools
import json
import os
@@ -10,6 +11,7 @@ import sys
import threading
import traceback
import urllib.parse
+import uuid
from concurrent import futures
from pathlib import Path
@@ -30,6 +32,7 @@ from .views.special import (
PatternPortfolioView,
AuthTokenView,
PermissionsDebugView,
+ MessagesDebugView,
)
from .views.table import RowView, TableView
from .renderer import json_renderer
@@ -156,6 +159,11 @@ async def favicon(scope, receive, send):
class Datasette:
+ # Message constants:
+ INFO = 1
+ WARNING = 2
+ ERROR = 3
+
def __init__(
self,
files,
@@ -289,7 +297,7 @@ class Datasette:
pm.hook.prepare_jinja2_environment(env=self.jinja_env)
self._register_renderers()
- self.permission_checks = collections.deque(maxlen=30)
+ self._permission_checks = collections.deque(maxlen=30)
self._root_token = os.urandom(32).hex()
def sign(self, value, namespace="default"):
@@ -423,6 +431,38 @@ class Datasette:
# pylint: disable=no-member
pm.hook.prepare_connection(conn=conn, database=database, datasette=self)
+ def add_message(self, request, message, type=INFO):
+ if not hasattr(request, "_messages"):
+ request._messages = []
+ request._messages_should_clear = False
+ request._messages.append((message, type))
+
+ def _write_messages_to_response(self, request, response):
+ if getattr(request, "_messages", None):
+ # Set those messages
+ cookie = SimpleCookie()
+ cookie["ds_messages"] = self.sign(request._messages, "messages")
+ cookie["ds_messages"]["path"] = "/"
+ # TODO: Co-exist with existing set-cookie headers
+ assert "set-cookie" not in response.headers
+ response.headers["set-cookie"] = cookie.output(header="").lstrip()
+ elif getattr(request, "_messages_should_clear", False):
+ cookie = SimpleCookie()
+ cookie["ds_messages"] = ""
+ cookie["ds_messages"]["path"] = "/"
+ # TODO: Co-exist with existing set-cookie headers
+ assert "set-cookie" not in response.headers
+ response.headers["set-cookie"] = cookie.output(header="").lstrip()
+
+ def _show_messages(self, request):
+ if getattr(request, "_messages", None):
+ request._messages_should_clear = True
+ messages = request._messages
+ request._messages = []
+ return messages
+ else:
+ return []
+
async def permission_allowed(
self, actor, action, resource_type=None, resource_identifier=None, default=False
):
@@ -445,7 +485,7 @@ class Datasette:
if result is None:
result = default
used_default = True
- self.permission_checks.append(
+ self._permission_checks.append(
{
"when": datetime.datetime.utcnow().isoformat(),
"actor": actor,
@@ -586,9 +626,9 @@ class Datasette:
},
}
- def _plugins(self, show_all=False):
+ def _plugins(self, request):
ps = list(get_plugins())
- if not show_all:
+ if not request.args.get("all"):
ps = [p for p in ps if p["name"] not in DEFAULT_PLUGINS]
return [
{
@@ -596,6 +636,7 @@ class Datasette:
"static": p["static_path"] is not None,
"templates": p["templates_path"] is not None,
"version": p.get("version"),
+ "hooks": p["hooks"],
}
for p in ps
]
@@ -783,7 +824,9 @@ class Datasette:
r"/-/versions(?P Set a message:Debug messages
+
+