mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
check_visibility can now take multiple permissions into account
Closes #1829
This commit is contained in:
parent
6887c12ea3
commit
78dad236df
10 changed files with 196 additions and 82 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import asyncio
|
||||
from typing import Sequence, Union, Tuple
|
||||
from typing import Sequence, Union, Tuple, Optional
|
||||
import asgi_csrf
|
||||
import collections
|
||||
import datetime
|
||||
|
|
@ -707,7 +707,7 @@ class Datasette:
|
|||
|
||||
Raises datasette.Forbidden() if any of the checks fail
|
||||
"""
|
||||
assert actor is None or isinstance(actor, dict)
|
||||
assert actor is None or isinstance(actor, dict), "actor must be None or a dict"
|
||||
for permission in permissions:
|
||||
if isinstance(permission, str):
|
||||
action = permission
|
||||
|
|
@ -732,23 +732,34 @@ class Datasette:
|
|||
else:
|
||||
raise Forbidden(action)
|
||||
|
||||
async def check_visibility(self, actor, action, resource):
|
||||
async def check_visibility(
|
||||
self,
|
||||
actor: dict,
|
||||
action: Optional[str] = None,
|
||||
resource: Optional[Union[str, Tuple[str, str]]] = None,
|
||||
permissions: Optional[
|
||||
Sequence[Union[Tuple[str, Union[str, Tuple[str, str]]], str]]
|
||||
] = None,
|
||||
):
|
||||
"""Returns (visible, private) - visible = can you see it, private = can others see it too"""
|
||||
visible = await self.permission_allowed(
|
||||
actor,
|
||||
action,
|
||||
resource=resource,
|
||||
default=True,
|
||||
)
|
||||
if not visible:
|
||||
if permissions:
|
||||
assert (
|
||||
not action and not resource
|
||||
), "Can't use action= or resource= with permissions="
|
||||
else:
|
||||
permissions = [(action, resource)]
|
||||
try:
|
||||
await self.ensure_permissions(actor, permissions)
|
||||
except Forbidden:
|
||||
return False, False
|
||||
private = not await self.permission_allowed(
|
||||
None,
|
||||
action,
|
||||
resource=resource,
|
||||
default=True,
|
||||
)
|
||||
return visible, private
|
||||
# User can see it, but can the anonymous user see it?
|
||||
try:
|
||||
await self.ensure_permissions(None, permissions)
|
||||
except Forbidden:
|
||||
# It's visible but private
|
||||
return True, True
|
||||
# It's visible to everyone
|
||||
return True, False
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 style="padding-left: 10px; border-left: 10px solid #{{ database_color(database) }}">{{ table }}: {{ ', '.join(primary_key_values) }}</h1>
|
||||
<h1 style="padding-left: 10px; border-left: 10px solid #{{ database_color(database) }}">{{ table }}: {{ ', '.join(primary_key_values) }}{% if private %} 🔒{% endif %}</h1>
|
||||
|
||||
{% block description_source_license %}{% include "_description_source_license.html" %}{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,13 +40,16 @@ class DatabaseView(DataView):
|
|||
raise NotFound("Database not found: {}".format(database_route))
|
||||
database = db.name
|
||||
|
||||
await self.ds.ensure_permissions(
|
||||
visible, private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
[
|
||||
permissions=[
|
||||
("view-database", database),
|
||||
"view-instance",
|
||||
],
|
||||
)
|
||||
if not visible:
|
||||
raise Forbidden("You do not have permission to view this database")
|
||||
|
||||
metadata = (self.ds.metadata("databases") or {}).get(database, {})
|
||||
self.ds.update_with_inherited_metadata(metadata)
|
||||
|
||||
|
|
@ -63,27 +66,27 @@ class DatabaseView(DataView):
|
|||
|
||||
views = []
|
||||
for view_name in await db.view_names():
|
||||
visible, private = await self.ds.check_visibility(
|
||||
view_visible, view_private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
"view-table",
|
||||
(database, view_name),
|
||||
)
|
||||
if visible:
|
||||
if view_visible:
|
||||
views.append(
|
||||
{
|
||||
"name": view_name,
|
||||
"private": private,
|
||||
"private": view_private,
|
||||
}
|
||||
)
|
||||
|
||||
tables = []
|
||||
for table in table_counts:
|
||||
visible, private = await self.ds.check_visibility(
|
||||
table_visible, table_private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
"view-table",
|
||||
(database, table),
|
||||
)
|
||||
if not visible:
|
||||
if not table_visible:
|
||||
continue
|
||||
table_columns = await db.table_columns(table)
|
||||
tables.append(
|
||||
|
|
@ -95,7 +98,7 @@ class DatabaseView(DataView):
|
|||
"hidden": table in hidden_table_names,
|
||||
"fts_table": await db.fts_table(table),
|
||||
"foreign_keys": all_foreign_keys[table],
|
||||
"private": private,
|
||||
"private": table_private,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -104,13 +107,13 @@ class DatabaseView(DataView):
|
|||
for query in (
|
||||
await self.ds.get_canned_queries(database, request.actor)
|
||||
).values():
|
||||
visible, private = await self.ds.check_visibility(
|
||||
query_visible, query_private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
"view-query",
|
||||
(database, query["name"]),
|
||||
)
|
||||
if visible:
|
||||
canned_queries.append(dict(query, private=private))
|
||||
if query_visible:
|
||||
canned_queries.append(dict(query, private=query_private))
|
||||
|
||||
async def database_actions():
|
||||
links = []
|
||||
|
|
@ -130,15 +133,13 @@ class DatabaseView(DataView):
|
|||
return (
|
||||
{
|
||||
"database": database,
|
||||
"private": private,
|
||||
"path": self.ds.urls.database(database),
|
||||
"size": db.size,
|
||||
"tables": tables,
|
||||
"hidden_count": len([t for t in tables if t["hidden"]]),
|
||||
"views": views,
|
||||
"queries": canned_queries,
|
||||
"private": not await self.ds.permission_allowed(
|
||||
None, "view-database", database, default=True
|
||||
),
|
||||
"allow_execute_sql": await self.ds.permission_allowed(
|
||||
request.actor, "execute-sql", database, default=True
|
||||
),
|
||||
|
|
@ -227,17 +228,17 @@ class QueryView(DataView):
|
|||
private = False
|
||||
if canned_query:
|
||||
# Respect canned query permissions
|
||||
await self.ds.ensure_permissions(
|
||||
visible, private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
[
|
||||
permissions=[
|
||||
("view-query", (database, canned_query)),
|
||||
("view-database", database),
|
||||
"view-instance",
|
||||
],
|
||||
)
|
||||
private = not await self.ds.permission_allowed(
|
||||
None, "view-query", (database, canned_query), default=True
|
||||
)
|
||||
if not visible:
|
||||
raise Forbidden("You do not have permission to view this query")
|
||||
|
||||
else:
|
||||
await self.ds.ensure_permissions(request.actor, [("execute-sql", database)])
|
||||
|
||||
|
|
|
|||
|
|
@ -23,25 +23,25 @@ class IndexView(BaseView):
|
|||
await self.ds.ensure_permissions(request.actor, ["view-instance"])
|
||||
databases = []
|
||||
for name, db in self.ds.databases.items():
|
||||
visible, database_private = await self.ds.check_visibility(
|
||||
database_visible, database_private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
"view-database",
|
||||
name,
|
||||
)
|
||||
if not visible:
|
||||
if not database_visible:
|
||||
continue
|
||||
table_names = await db.table_names()
|
||||
hidden_table_names = set(await db.hidden_table_names())
|
||||
|
||||
views = []
|
||||
for view_name in await db.view_names():
|
||||
visible, private = await self.ds.check_visibility(
|
||||
view_visible, view_private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
"view-table",
|
||||
(name, view_name),
|
||||
)
|
||||
if visible:
|
||||
views.append({"name": view_name, "private": private})
|
||||
if view_visible:
|
||||
views.append({"name": view_name, "private": view_private})
|
||||
|
||||
# Perform counts only for immutable or DBS with <= COUNT_TABLE_LIMIT tables
|
||||
table_counts = {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from datasette.utils.asgi import NotFound
|
||||
from datasette.utils.asgi import NotFound, Forbidden
|
||||
from datasette.database import QueryInterrupted
|
||||
from .base import DataView
|
||||
from datasette.utils import (
|
||||
|
|
@ -21,14 +21,19 @@ class RowView(DataView):
|
|||
except KeyError:
|
||||
raise NotFound("Database not found: {}".format(database_route))
|
||||
database = db.name
|
||||
await self.ds.ensure_permissions(
|
||||
|
||||
# Ensure user has permission to view this row
|
||||
visible, private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
[
|
||||
permissions=[
|
||||
("view-table", (database, table)),
|
||||
("view-database", database),
|
||||
"view-instance",
|
||||
],
|
||||
)
|
||||
if not visible:
|
||||
raise Forbidden("You do not have permission to view this table")
|
||||
|
||||
pk_values = urlsafe_components(request.url_vars["pks"])
|
||||
try:
|
||||
db = self.ds.get_database(route=database_route)
|
||||
|
|
@ -55,6 +60,7 @@ class RowView(DataView):
|
|||
for column in display_columns:
|
||||
column["sortable"] = False
|
||||
return {
|
||||
"private": private,
|
||||
"foreign_key_tables": await self.foreign_key_tables(
|
||||
database, table, pk_values
|
||||
),
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from datasette.utils import (
|
|||
urlsafe_components,
|
||||
value_as_boolean,
|
||||
)
|
||||
from datasette.utils.asgi import BadRequest, NotFound
|
||||
from datasette.utils.asgi import BadRequest, Forbidden, NotFound
|
||||
from datasette.filters import Filters
|
||||
from .base import DataView, DatasetteError, ureg
|
||||
from .database import QueryView
|
||||
|
|
@ -213,18 +213,16 @@ class TableView(DataView):
|
|||
raise NotFound(f"Table not found: {table_name}")
|
||||
|
||||
# Ensure user has permission to view this table
|
||||
await self.ds.ensure_permissions(
|
||||
visible, private = await self.ds.check_visibility(
|
||||
request.actor,
|
||||
[
|
||||
permissions=[
|
||||
("view-table", (database_name, table_name)),
|
||||
("view-database", database_name),
|
||||
"view-instance",
|
||||
],
|
||||
)
|
||||
|
||||
private = not await self.ds.permission_allowed(
|
||||
None, "view-table", (database_name, table_name), default=True
|
||||
)
|
||||
if not visible:
|
||||
raise Forbidden("You do not have permission to view this table")
|
||||
|
||||
# Handle ?_filter_column and redirect, if present
|
||||
redirect_params = filters_should_redirect(request.args)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue