-
-
Notifications
You must be signed in to change notification settings - Fork 690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cascade for restricted token view-table/view-database/view-instance operations #2154
Conversation
Also includes a prototype implementation of --actor option from #2153 which I'm using for testing this.
Can be tested with: pip install https://github.com/simonw/datasette/archive/6d57a8c23043e99b27f7a2afbe58f4d58815fd51.zip |
Codecov ReportPatch coverage:
Additional details and impacted files@@ Coverage Diff @@
## main #2154 +/- ##
==========================================
+ Coverage 92.82% 92.85% +0.03%
==========================================
Files 40 40
Lines 5948 6008 +60
==========================================
+ Hits 5521 5579 +58
- Misses 427 429 +2
☔ View full report in Codecov by Sentry. |
Notes on manual testing so far - it looks like this might be working! |
datasette/default_permissions.py
Outdated
# Special case for view-instance: it's allowed if there are any view-database | ||
# or view-table permissions defined | ||
if action == "view-instance": | ||
all_rules = _r.get("a") or [] | ||
if ( | ||
"view-database" in all_rules | ||
or "vd" in all_rules | ||
or "view-table" in all_rules | ||
or "vt" in all_rules | ||
): | ||
return None | ||
database_rules = _r.get("d") or {} | ||
for rules in database_rules.values(): | ||
if ( | ||
"vd" in rules | ||
or "view-database" in rules | ||
or "vt" in rules | ||
or "view-table" in rules | ||
): | ||
return None | ||
# Now check resources | ||
resource_rules = _r.get("r") or {} | ||
for _database, resources in resource_rules.items(): | ||
for rules in resources.values(): | ||
if "vt" in rules or "view-table" in rules: | ||
return None | ||
|
||
# Special case for view-database: it's allowed if there are any view-table permissions | ||
# defined within that database | ||
if action == "view-database": | ||
database_name = resource | ||
all_rules = _r.get("a") or [] | ||
if ( | ||
"view-database" in all_rules | ||
or "vd" in all_rules | ||
or "view-table" in all_rules | ||
or "vt" in all_rules | ||
): | ||
return None | ||
database_rules = (_r.get("d") or {}).get(database_name) or {} | ||
if "vt" in database_rules or "view-table" in database_rules: | ||
return None | ||
resource_rules = _r.get("r") or {} | ||
resources_in_database = resource_rules.get(database_name) or {} | ||
for rules in resources_in_database.values(): | ||
if "vt" in rules or "view-table" in rules: | ||
return None | ||
|
||
if action == "view-table": | ||
# Can view table if they have view-table in database or instance too | ||
database_name = resource[0] | ||
all_rules = _r.get("a") or [] | ||
if "view-table" in all_rules or "vt" in all_rules: | ||
return None | ||
database_rules = (_r.get("d") or {}).get(database_name) or {} | ||
if "vt" in database_rules or "view-table" in database_rules: | ||
return None | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is pretty complicated. Maybe it would be simple and better if the fact that a permission cascades like this was modeled on the permission classes here instead:
datasette/datasette/default_permissions.py
Lines 7 to 38 in 2e28258
@hookimpl | |
def register_permissions(): | |
return ( | |
# name, abbr, description, takes_database, takes_resource, default | |
Permission( | |
"view-instance", "vi", "View Datasette instance", False, False, True | |
), | |
Permission("view-database", "vd", "View database", True, False, True), | |
Permission( | |
"view-database-download", "vdd", "Download database file", True, False, True | |
), | |
Permission("view-table", "vt", "View table", True, True, True), | |
Permission("view-query", "vq", "View named query results", True, True, True), | |
Permission( | |
"execute-sql", "es", "Execute read-only SQL queries", True, False, True | |
), | |
Permission( | |
"permissions-debug", | |
"pd", | |
"Access permission debug tool", | |
False, | |
False, | |
False, | |
), | |
Permission("debug-menu", "dm", "View debug menu items", False, False, False), | |
# Write API permissions | |
Permission("insert-row", "ir", "Insert rows", True, True, False), | |
Permission("delete-row", "dr", "Delete rows", True, True, False), | |
Permission("update-row", "ur", "Update rows", True, True, False), | |
Permission("create-table", "ct", "Create tables", True, False, False), | |
Permission("drop-table", "dt", "Drop tables", True, True, False), | |
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Permissions is currently a namedtuple
:
datasette/datasette/permissions.py
Lines 3 to 6 in 2e28258
Permission = collections.namedtuple( | |
"Permission", | |
("name", "abbr", "description", "takes_database", "takes_resource", "default"), | |
) |
It's used by a few plugins too (post-1.0 alpha) via this plugin hook: https://docs.datasette.io/en/1.0a4/plugin_hooks.html#register-permissions-datasette
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could add a implies_can_view=False
property to that - if it's true, then it means that having this permission implies that you can view the database (if the permission takes_database
) and the instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or... I could have a special subclass of Permission
called PermissionImpliesView
which is reserved for use by core, and isn't something that's documented for plugins to register their own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to upgrade Permission
from a named tuple to a dataclass
.
Should this have datasette/datasette/default_permissions.py Lines 20 to 22 in d64a989
|
Reminder that I also need to confirm that |
Code for this might be cleaner with a |
The code to refactor is this: datasette/datasette/default_permissions.py Lines 181 to 280 in d64a989
I'm going to turn that into a more general |
In that last commit I also upgraded Last step is to refactor the code to use that new property. |
Refs:
Also includes a prototype implementation of
--actor option
which I'm using for testing this, from:📚 Documentation preview 📚: https://datasette--2154.org.readthedocs.build/en/2154/