* Fix flaky test_database_page test with deterministic ordering
- Add ORDER BY to table_names() query in database.py
- Sort foreign keys deterministically in get_all_foreign_keys()
- Refactor test_database_page to use property-based assertions instead of
500+ lines of hardcoded expected data
- Run blacken-docs on plugin_hooks.rst
* Update test_row_foreign_key_tables for new deterministic FK ordering
The foreign keys are now sorted by (other_table, column, other_column),
so complex_foreign_keys comes before foreign_key_references alphabetically.
* Update test_table_names for new alphabetical ordering
The table_names() method now returns tables sorted alphabetically.
* Fix for test that fails prior to SQLite 3.37
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix test isolation bug in test_startup_error_from_plugin_is_click_exception
The test creates a plugin that raises StartupError("boom") and registers it
in the global plugin manager (pm). Without cleanup, this plugin leaks to
subsequent tests, causing test_setting_boolean_validation_false_values to
fail with "Error: boom" instead of "Forbidden".
Add try/finally block to ensure the plugin is unregistered after the test
completes, following the established cleanup pattern used elsewhere in
the test suite.
* Fix blacken-docs formatting in plugin_hooks.rst
Apply blacken-docs formatting to code example that exceeded
the 60 character line limit.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Split default_permissions.py into a package, refs #2602
* Remove unused is_resource_allowed() method, improve test coverage
- Remove dead code: is_resource_allowed() method was never called
- Change isinstance check to assertion with error message
- Add test cases for table-level restrictions in restrictions_allow_action()
- Coverage for restrictions.py improved from 79% to 99%
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Additional permission test for gap spotted by coverage
* Issue 2429 indicates the possiblity of an open redirect
The 404 processing ends up redirecting a request with multiple path
slashes to that site, i.e.
https://my-site//shedcode.co.uk will redirect to https://shedcode.co.uk
This commit uses a regular expression to remove the multiple leading
slashes before redirecting.
Implement INTERSECT-based actor restrictions to prevent permission bypass
Actor restrictions are now implemented as SQL filters using INTERSECT rather
than as deny/allow permission rules. This ensures restrictions act as hard
limits that cannot be overridden by other permission plugins or config blocks.
Previously, actor restrictions (_r in actor dict) were implemented by
generating permission rules with deny/allow logic. This approach had a
critical flaw: database-level config allow blocks could bypass table-level
restrictions, granting access to tables not in the actor's allowlist.
The new approach separates concerns:
- Permission rules determine what's allowed based on config and plugins
- Restriction filters limit the result set to only allowlisted resources
- Restrictions use INTERSECT to ensure all restriction criteria are met
- Database-level restrictions (parent, NULL) properly match all child tables
Implementation details:
- Added restriction_sql field to PermissionSQL dataclass
- Made PermissionSQL.sql optional to support restriction-only plugins
- Updated actor_restrictions_sql() to return restriction filters instead of rules
- Modified SQL builders to apply restrictions via INTERSECT and EXISTS clauses
Closes#2572