1- from datasette import hookimpl , Forbidden , Response , NotFound , Permission
1+ from datasette import hookimpl , Forbidden , Response , NotFound
2+ from datasette .permissions import Action , PermissionSQL
3+ from datasette .resources import DatabaseResource , QueryResource , TableResource
24from urllib .parse import quote_plus , unquote_plus
3- from typing import Tuple
5+ from typing import Sequence , Tuple , Union
46
57CREATE_TABLES_SQL = """
68create table if not exists public_tables (
2830);
2931""" .strip ()
3032
33+ PermissionSpec = Union [str , Tuple [str , str ], Tuple [str , str , str ]]
34+
35+
36+ def _resource_for_action (action , parent = None , child = None ):
37+ if action in ("datasette-public" , "execute-sql" , "view-database" ):
38+ if parent is None :
39+ return None
40+ return DatabaseResource (database = parent )
41+ if action == "view-table" :
42+ if parent is None or child is None :
43+ return None
44+ return TableResource (database = parent , table = child )
45+ if action == "view-query" :
46+ if parent is None or child is None :
47+ return None
48+ return QueryResource (database = parent , query = child )
49+ return None
50+
51+
52+ async def _check_permissions_visibility (
53+ datasette , actor , permissions : Sequence [PermissionSpec ]
54+ ) -> Tuple [bool , bool ]:
55+ for permission in permissions :
56+ if isinstance (permission , tuple ):
57+ action = permission [0 ]
58+ parent = permission [1 ] if len (permission ) > 1 else None
59+ child = permission [2 ] if len (permission ) > 2 else None
60+ else :
61+ action = permission
62+ parent = child = None
63+ resource = _resource_for_action (action , parent , child )
64+ visible , private = await datasette .check_visibility (
65+ actor ,
66+ action = action ,
67+ resource = resource ,
68+ )
69+ if visible :
70+ return visible , private
71+ return False , False
72+
3173
3274@hookimpl
3375def startup (datasette ):
@@ -48,45 +90,63 @@ async def inner():
4890
4991
5092@hookimpl
51- def register_permissions ():
93+ def register_actions ():
5294 return [
53- Permission (
95+ Action (
5496 name = "datasette-public" ,
55- abbr = None ,
56- description = "Make tables and databases public/private" ,
57- takes_database = True ,
58- takes_resource = False ,
59- default = False ,
97+ description = "Make tables and databases public or private" ,
98+ resource_class = DatabaseResource ,
6099 ),
61100 ]
62101
63102
64- @hookimpl
65- def permission_allowed (datasette , action , actor , resource ):
66- async def inner ():
67- # Root actor can always edit public status
68- if actor and actor .get ("id" ) == "root" and action == "datasette-public" :
69- return True
70- if action == "execute-sql" and not actor :
71- # We now have an opinion on execute-sql for anonymous users
72- _ , allow_sql = await database_privacy_settings (datasette , resource )
73- return allow_sql
74- if action not in ("view-table" , "view-database" , "view-query" ):
75- return None
76- if action == "view-table" and await table_is_public (
77- datasette , resource [0 ], resource [1 ]
78- ):
79- return True
80- if action == "view-database" :
81- is_public , _ = await database_privacy_settings (datasette , resource )
82- if is_public :
83- return True
84- if action == "view-query" and await query_is_public (
85- datasette , resource [0 ], resource [1 ]
86- ):
87- return True
88-
89- return inner
103+ @hookimpl (specname = "permission_resources_sql" )
104+ def permission_resources_sql (datasette , actor , action ):
105+ if action == "view-database" :
106+ return PermissionSQL (
107+ sql = """
108+ select database_name as parent,
109+ null as child,
110+ 1 as allow,
111+ 'datasette-public database is public' as reason
112+ from public_databases
113+ """ ,
114+ )
115+ if action == "view-table" :
116+ return PermissionSQL (
117+ sql = """
118+ select database_name as parent,
119+ table_name as child,
120+ 1 as allow,
121+ 'datasette-public table is public' as reason
122+ from public_tables
123+ """ ,
124+ )
125+ if action == "view-query" :
126+ return PermissionSQL (
127+ sql = """
128+ select database_name as parent,
129+ query_name as child,
130+ 1 as allow,
131+ 'datasette-public query is public' as reason
132+ from public_queries
133+ """ ,
134+ )
135+ if action == "execute-sql" :
136+ return PermissionSQL (
137+ sql = """
138+ select database_name as parent,
139+ null as child,
140+ case when allow_sql = 1 then 1 else 0 end as allow,
141+ case
142+ when allow_sql = 1 then 'datasette-public SQL enabled'
143+ else 'datasette-public SQL disabled'
144+ end as reason
145+ from public_databases
146+ where :actor is null
147+ """ ,
148+ )
149+ return None
90150
91151
92152async def table_is_public (datasette , database_name , table_name ):
@@ -120,14 +180,21 @@ async def query_is_public(datasette, database_name, query_name):
120180
121181
122182@hookimpl
123- def table_actions (datasette , actor , database , table ):
183+ def table_actions (datasette , actor , database , table , request ):
124184 async def inner ():
125- if not await datasette .permission_allowed (
126- actor , "datasette-public" , resource = database
185+ resource = _resource_for_action ("datasette-public" , database )
186+ if not resource :
187+ return
188+ if not await datasette .allowed (
189+ action = "datasette-public" ,
190+ resource = resource ,
191+ actor = actor ,
127192 ):
128193 return
129- database_visible , database_private = await datasette .check_visibility (
130- actor , permissions = [("view-database" , database ), "view-instance" ]
194+ database_visible , database_private = await _check_permissions_visibility (
195+ datasette ,
196+ actor ,
197+ [("view-database" , database ), "view-instance" ],
131198 )
132199 if database_visible and not database_private :
133200 return
@@ -155,14 +222,21 @@ async def inner():
155222
156223
157224@hookimpl
158- def database_actions (datasette , actor , database ):
225+ def database_actions (datasette , actor , database , request ):
159226 async def inner ():
160- if not await datasette .permission_allowed (
161- actor , "datasette-public" , resource = database
227+ resource = _resource_for_action ("datasette-public" , database )
228+ if not resource :
229+ return
230+ if not await datasette .allowed (
231+ action = "datasette-public" ,
232+ resource = resource ,
233+ actor = actor ,
162234 ):
163235 return
164- instance_visible , instance_private = await datasette .check_visibility (
165- actor , permissions = ["view-instance" ]
236+ instance_visible , instance_private = await _check_permissions_visibility (
237+ datasette ,
238+ actor ,
239+ ["view-instance" ],
166240 )
167241 if instance_visible and not instance_private :
168242 return
@@ -186,21 +260,28 @@ async def inner():
186260
187261
188262@hookimpl
189- def view_actions (datasette , actor , database , view ):
190- return table_actions (datasette , actor , database , view )
263+ def view_actions (datasette , actor , database , view , request ):
264+ return table_actions (datasette , actor , database , view , request )
191265
192266
193267@hookimpl
194- def query_actions (datasette , actor , database , query_name ):
268+ def query_actions (datasette , actor , database , query_name , request , sql , params ):
195269 async def inner ():
196- if not await datasette .permission_allowed (
197- actor , "datasette-public" , resource = database
270+ resource = _resource_for_action ("datasette-public" , database )
271+ if not resource :
272+ return
273+ if not await datasette .allowed (
274+ action = "datasette-public" ,
275+ resource = resource ,
276+ actor = actor ,
198277 ):
199278 return
200- database_visible , database_private = await datasette .check_visibility (
201- actor , permissions = [("view-database" , database ), "view-instance" ]
279+ database_visible , database_private = await _check_permissions_visibility (
280+ datasette ,
281+ actor ,
282+ [("view-database" , database ), "view-instance" ],
202283 )
203- if (not database_visible ) or database_private :
284+ if (not database_visible ) or ( not database_private ) :
204285 return
205286 is_private = not await query_is_public (datasette , database , query_name )
206287 return [
@@ -221,8 +302,11 @@ async def inner():
221302
222303
223304async def check_permissions (datasette , request , database ):
224- if not await datasette .permission_allowed (
225- request .actor , "datasette-public" , resource = database
305+ resource = _resource_for_action ("datasette-public" , database )
306+ if not resource or not await datasette .allowed (
307+ action = "datasette-public" ,
308+ resource = resource ,
309+ actor = request .actor ,
226310 ):
227311 raise Forbidden ("Permission denied for changing table privacy" )
228312
@@ -336,8 +420,10 @@ async def change_table_privacy(request, datasette):
336420
337421 is_private = not await table_is_public (datasette , database_name , table )
338422
339- database_visible , database_private = await datasette .check_visibility (
340- request .actor , permissions = [("view-database" , database_name ), "view-instance" ]
423+ database_visible , database_private = await _check_permissions_visibility (
424+ datasette ,
425+ request .actor ,
426+ [("view-database" , database_name ), "view-instance" ],
341427 )
342428 database_is_public = database_visible and not database_private
343429
@@ -441,8 +527,10 @@ async def change_database_privacy(request, datasette):
441527
442528 is_public , allow_sql = await database_privacy_settings (datasette , database_name )
443529
444- instance_visible , instance_private = await datasette .check_visibility (
445- request .actor , permissions = ["view-instance" ]
530+ instance_visible , instance_private = await _check_permissions_visibility (
531+ datasette ,
532+ request .actor ,
533+ ["view-instance" ],
446534 )
447535 instance_is_public = instance_visible and not instance_private
448536
@@ -566,8 +654,10 @@ async def change_query_privacy(request, datasette):
566654
567655 is_private = not await query_is_public (datasette , database_name , query )
568656
569- database_visible , database_private = await datasette .check_visibility (
570- request .actor , permissions = [("view-database" , database_name ), "view-instance" ]
657+ database_visible , database_private = await _check_permissions_visibility (
658+ datasette ,
659+ request .actor ,
660+ [("view-database" , database_name ), "view-instance" ],
571661 )
572662 database_is_public = database_visible and not database_private
573663
0 commit comments