Added permission check to every view, closes #808

This commit is contained in:
Simon Willison 2020-06-06 22:30:36 -07:00
commit 86dec9e8ff
13 changed files with 220 additions and 2 deletions

View file

@ -49,6 +49,7 @@ from .utils import (
)
from .utils.asgi import (
AsgiLifespan,
Forbidden,
NotFound,
Request,
Response,
@ -1003,6 +1004,10 @@ class DatasetteRouter(AsgiRouter):
status = 404
info = {}
message = exception.args[0]
elif isinstance(exception, Forbidden):
status = 403
info = {}
message = exception.args[0]
elif isinstance(exception, DatasetteError):
status = exception.status
info = exception.error_dict

View file

@ -47,7 +47,7 @@
</h2>
<p><strong>Actor:</strong> {{ check.actor|tojson }}</p>
{% if check.resource_type %}
<p><strong>Resource:</strong> {{ check.resource_type }}: {{ check.resource_identifier }}</p>
<p><strong>Resource:</strong> {{ check.resource_type }} = {{ check.resource_identifier }}</p>
{% endif %}
</div>
{% endfor %}

View file

@ -13,6 +13,10 @@ class NotFound(Exception):
pass
class Forbidden(Exception):
pass
class Request:
def __init__(self, scope, receive):
self.scope = scope

View file

@ -29,6 +29,7 @@ from datasette.utils.asgi import (
AsgiWriter,
AsgiRouter,
AsgiView,
Forbidden,
NotFound,
Response,
)
@ -63,6 +64,19 @@ class BaseView(AsgiView):
response.body = b""
return response
async def check_permission(
self, request, action, resource_type=None, resource_identifier=None
):
ok = await self.ds.permission_allowed(
request.scope.get("actor"),
action,
resource_type=resource_type,
resource_identifier=resource_identifier,
default=True,
)
if not ok:
raise Forbidden(action)
def database_url(self, database):
db = self.ds.databases[database]
base_url = self.ds.config("base_url")

View file

@ -19,6 +19,7 @@ class DatabaseView(DataView):
name = "database"
async def data(self, request, database, hash, default_labels=False, _size=None):
await self.check_permission(request, "view-database", "database", database)
metadata = (self.ds.metadata("databases") or {}).get(database, {})
self.ds.update_with_inherited_metadata(metadata)
@ -89,6 +90,9 @@ class DatabaseDownload(DataView):
name = "database_download"
async def view_get(self, request, database, hash, correct_hash_present, **kwargs):
await self.check_permission(
request, "view-database-download", "database", database
)
if database not in self.ds.databases:
raise DatasetteError("Invalid database", status=404)
db = self.ds.databases[database]
@ -128,6 +132,10 @@ class QueryView(DataView):
# Respect canned query permissions
if canned_query:
await self.check_permission(
request, "view-query", "query", (database, canned_query)
)
# TODO: fix this to use that permission check
if not actor_matches_allow(
request.scope.get("actor", None), metadata.get("allow")
):

View file

@ -22,6 +22,7 @@ class IndexView(BaseView):
self.ds = datasette
async def get(self, request, as_format):
await self.check_permission(request, "view-index")
databases = []
for name, db in self.ds.databases.items():
table_names = await db.table_names()

View file

@ -267,6 +267,8 @@ class TableView(RowTableShared):
if not is_view and not table_exists:
raise NotFound("Table not found: {}".format(table))
await self.check_permission(request, "view-table", "table", (database, table))
pks = await db.primary_keys(table)
table_columns = await db.table_columns(table)
@ -844,6 +846,9 @@ class RowView(RowTableShared):
async def data(self, request, database, hash, table, pk_path, default_labels=False):
pk_values = urlsafe_components(pk_path)
await self.check_permission(
request, "view-row", "row", tuple([database, table] + list(pk_values))
)
db = self.ds.databases[database]
pks = await db.primary_keys(table)
use_rowid = not pks