mirror of
https://github.com/simonw/datasette.git
synced 2026-10-01 14:24:08 +02:00
Added /-/permissions debug tool, closes #788
Also started the authentication.rst docs page, refs #786. Part of authentication work, refs #699.
This commit is contained in:
parent
57cf5139c5
commit
dfdbdf378a
8 changed files with 152 additions and 3 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import collections
|
||||
import datetime
|
||||
import hashlib
|
||||
import itertools
|
||||
import json
|
||||
|
|
@ -24,7 +25,12 @@ import uvicorn
|
|||
from .views.base import DatasetteError, ureg, AsgiRouter
|
||||
from .views.database import DatabaseDownload, DatabaseView
|
||||
from .views.index import IndexView
|
||||
from .views.special import JsonDataView, PatternPortfolioView, AuthTokenView
|
||||
from .views.special import (
|
||||
JsonDataView,
|
||||
PatternPortfolioView,
|
||||
AuthTokenView,
|
||||
PermissionsDebugView,
|
||||
)
|
||||
from .views.table import RowView, TableView
|
||||
from .renderer import json_renderer
|
||||
from .database import Database, QueryInterrupted
|
||||
|
|
@ -283,6 +289,7 @@ class Datasette:
|
|||
pm.hook.prepare_jinja2_environment(env=self.jinja_env)
|
||||
|
||||
self._register_renderers()
|
||||
self.permission_checks = collections.deque(maxlen=30)
|
||||
self._root_token = os.urandom(32).hex()
|
||||
|
||||
def sign(self, value, namespace="default"):
|
||||
|
|
@ -420,6 +427,7 @@ class Datasette:
|
|||
self, actor, action, resource_type=None, resource_identifier=None, default=False
|
||||
):
|
||||
"Check permissions using the permissions_allowed plugin hook"
|
||||
result = None
|
||||
for check in pm.hook.permission_allowed(
|
||||
datasette=self,
|
||||
actor=actor,
|
||||
|
|
@ -432,8 +440,23 @@ class Datasette:
|
|||
if asyncio.iscoroutine(check):
|
||||
check = await check
|
||||
if check is not None:
|
||||
return check
|
||||
return default
|
||||
result = check
|
||||
used_default = False
|
||||
if result is None:
|
||||
result = default
|
||||
used_default = True
|
||||
self.permission_checks.append(
|
||||
{
|
||||
"when": datetime.datetime.utcnow().isoformat(),
|
||||
"actor": actor,
|
||||
"action": action,
|
||||
"resource_type": resource_type,
|
||||
"resource_identifier": resource_identifier,
|
||||
"used_default": used_default,
|
||||
"result": result,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
|
|
@ -782,6 +805,9 @@ class Datasette:
|
|||
add_route(
|
||||
AuthTokenView.as_asgi(self), r"/-/auth-token$",
|
||||
)
|
||||
add_route(
|
||||
PermissionsDebugView.as_asgi(self), r"/-/permissions$",
|
||||
)
|
||||
add_route(
|
||||
PatternPortfolioView.as_asgi(self), r"/-/patterns$",
|
||||
)
|
||||
|
|
|
|||
7
datasette/default_permissions.py
Normal file
7
datasette/default_permissions.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from datasette import hookimpl
|
||||
|
||||
|
||||
@hookimpl
|
||||
def permission_allowed(actor, action, resource_type, resource_identifier):
|
||||
if actor and actor.get("id") == "root" and action == "permissions-debug":
|
||||
return True
|
||||
|
|
@ -10,6 +10,7 @@ DEFAULT_PLUGINS = (
|
|||
"datasette.facets",
|
||||
"datasette.sql_functions",
|
||||
"datasette.actor_auth_cookie",
|
||||
"datasette.default_permissions",
|
||||
)
|
||||
|
||||
pm = pluggy.PluginManager("datasette")
|
||||
|
|
|
|||
55
datasette/templates/permissions_debug.html
Normal file
55
datasette/templates/permissions_debug.html
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Debug permissions{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<style type="text/css">
|
||||
.check-result-true {
|
||||
color: green;
|
||||
}
|
||||
.check-result-false {
|
||||
color: red;
|
||||
}
|
||||
.check h2 {
|
||||
font-size: 1em
|
||||
}
|
||||
.check-action, .check-when, .check-result {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block nav %}
|
||||
<p class="crumbs">
|
||||
<a href="{{ base_url }}">home</a>
|
||||
</p>
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1>Recent permissions checks</h1>
|
||||
|
||||
{% for check in permission_checks %}
|
||||
<div class="check">
|
||||
<h2>
|
||||
<span class="check-action">{{ check.action }}</span>
|
||||
checked at
|
||||
<span class="check-when">{{ check.when }}</span>
|
||||
{% if check.result %}
|
||||
<span class="check-result check-result-true">✓</span>
|
||||
{% else %}
|
||||
<span class="check-result check-result-false">✗</span>
|
||||
{% endif %}
|
||||
{% if check.used_default %}
|
||||
<span class="check-used-default">(used default)</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<p><strong>Actor:</strong> {{ check.actor|tojson }}</p>
|
||||
{% if check.resource_type %}
|
||||
<p><strong>Resource:</strong> {{ check.resource_type }}: {{ check.resource_identifier }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -76,3 +76,21 @@ class AuthTokenView(BaseView):
|
|||
return response
|
||||
else:
|
||||
return Response("Invalid token", status=403)
|
||||
|
||||
|
||||
class PermissionsDebugView(BaseView):
|
||||
name = "permissions_debug"
|
||||
|
||||
def __init__(self, datasette):
|
||||
self.ds = datasette
|
||||
|
||||
async def get(self, request):
|
||||
if not await self.ds.permission_allowed(
|
||||
request.scope.get("actor"), "permissions-debug"
|
||||
):
|
||||
return Response("Permission denied", status=403)
|
||||
return await self.render(
|
||||
["permissions_debug.html"],
|
||||
request,
|
||||
{"permission_checks": reversed(self.ds.permission_checks)},
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue