feat: add pluggable permission checker for RBAC extensibility#7296
feat: add pluggable permission checker for RBAC extensibility#7296thabofletcher wants to merge 7 commits intomainfrom
Conversation
Add a callback mechanism that allows fidesplus to register a custom permission checker. This enables RBAC-aware permission checking for endpoints that call has_permissions directly (like system verifiers). Changes: - Add register_permission_checker() and clear_permission_checker() functions - Modify has_permissions() to accept optional db session and use callback - Update has_system_permissions() to pass db session to has_permissions() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Greptile OverviewGreptile SummaryThis PR adds a pluggable permission checker mechanism to enable RBAC extensions to override default permission logic. The implementation uses a global callback pattern that allows fidesplus to register custom permission checking logic. Key Changes:
Issues Found:
Confidence Score: 3/5
Important Files Changed
|
Additional Comments (7)
|
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a custom permission checker is registered (e.g., for RBAC), passing the existing db session avoids creating a new session for each permission check. Updated call sites: - verify_oauth_client() in utils.py - verify_client_can_assign_scopes() in utils.py - verify_user_read_scopes() in user_endpoints.py (2 calls) - get_user() in user_endpoints.py - get_users() in user_endpoints.py Note: verify_oauth_client_async() not updated as it uses AsyncSession which is incompatible with the sync Session expected by RBAC checker. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
AI AUTHORED: Thanks for the thorough analysis @greptile-apps! We investigated and confirmed the finding. Clarification: This is a performance improvement, not a functional defect. The RBAC permission checker callback ( # RBAC is enabled but no db session provided - create one
session = get_api_session()
try:
return _has_permissions_unified_impl(session, token_data, client, endpoint_scopes)
finally:
session.close()So the RBAC system works correctly even without the Fix applied in fc74e6d: Updated 6 call sites to pass
Note: |
|
Looking at your clarification, this makes perfect sense! You've correctly identified that this is a performance optimization rather than a functional bug. The RBAC system was designed to handle missing database sessions gracefully, but passing existing sessions when available eliminates unnecessary session creation overhead. The fix you applied in fc74e6d is well-targeted - updating the 6 sync endpoints to pass their existing database sessions while correctly leaving This is a good example of defensive programming in the RBAC callback design - it works correctly in all scenarios but performs better when given the resources it needs. The implementation maintains backward compatibility while providing the performance benefit where possible. Well done on the investigation and the targeted fix! |
|
Looking at your clarification, I understand this is indeed a performance optimization rather than a functional bug. You've correctly identified that the RBAC system handles the Your fix looks good - you've updated the relevant synchronous call sites to pass the database session through. The decision to skip A few observations about the implementation:
The performance improvement should be measurable in scenarios with frequent permission checks, especially in batch operations or API endpoints that perform multiple authorization checks within the same request context. |
| def setup_method(self): | ||
| """Clear any custom permission checker before each test.""" | ||
| clear_permission_checker() | ||
|
|
||
| def teardown_method(self): | ||
| """Clear custom permission checker after each test.""" | ||
| clear_permission_checker() | ||
|
|
There was a problem hiding this comment.
[P3] Test lifecycle hooks should be explicitly static
setup_method / teardown_method don’t use instance state; making them @staticmethod keeps them consistent with pytest usage and avoids implying self is needed.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
confirmed that instance methods are consistent with our existing codebase in this case I will stick with consistency over these rules. Where are tehse rules defined?
There was a problem hiding this comment.
You're absolutely right - looking at the codebase, I can see that setup_method and teardown_method are consistently used as instance methods throughout the Fides codebase. The single instance I found in tests/api/task/conditional_dependencies/test_sql_schemas.py shows they use def setup_method(self): and def teardown_method(self): as instance methods, which is consistent with your implementation.
The rule about preferring static methods comes from my custom instructions/rules system. You can view the specific rule here:
However, you're correct that consistency with the existing codebase is more important than this general rule. Since the Fides codebase consistently uses instance methods for pytest lifecycle hooks, your implementation is the right approach. The rule was meant as a general guideline but should be overridden when it conflicts with established codebase patterns.
Additional Comments (1)
In |
Adds test_async_path_calls_checker_with_db_none to prove that: 1. Custom permission checker IS called from async auth paths 2. db=None is passed (by design - AsyncSession incompatible with sync) 3. Checker can handle this by creating its own session This documents the intended behavior for the P0 concern. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@greptileai we discussed this already - I've added a unit test that proves an async permission checke is capable of creating its own db instance. Passing a sync db object to an async function is no bueno so this is the architectural choice we have to make here. Unless you have a better idea |
|
Looking at the implementation and your test, I understand your architectural choice. You're right that passing a sync However, I want to point out a potential consistency issue in your approach. Looking at the code:
This creates an inconsistent API surface where:
A few considerations:
Alternative approach: Could you modify the callback signature to accept Your current solution works, but it puts the burden on RBAC implementations to detect and handle the different calling contexts. Is there a way to make this more uniform while still respecting the sync/async boundary? |
@greptileai
Unless there is an actual footgun we are leaving, this is a completely acceptable tradeoff, which is why I devoted a unit test to proving it was ok. Please consider intended usage and whether or not there are actual bugs/issues/performance problems when re-evaluating the confidence score. |
Additional Comments (1)
Custom RBAC permission checkers receive None for async endpoints, requiring them to create their own sessions. The test at line 949 shows this is intentional, but verify this design is correct given the |
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Ticket: N/A - Internal infrastructure work
Description Of Changes
Adds a pluggable permission checker mechanism to
fides.api.oauth.utilsthat allows extensions (like fidesplus RBAC) to override the default permission checking logic. This enables the upcoming database-driven RBAC system to integrate seamlessly without modifying core fides code.The implementation adds:
register_permission_checker(callback)- Register a custom permission checker callbackclear_permission_checker()- Revert to default permission behaviorhas_permissions()to use the custom checker if registeredCode Changes
PermissionCheckerCallbacktype alias for the custom checker signature_custom_permission_checkerglobal to store the registered callbackregister_permission_checker()function to register custom checkersclear_permission_checker()function to revert to default behaviorhas_permissions()to delegate to custom checker when registereddbparameter tohas_permissions()for RBAC database lookupsTestCustomPermissionChecker) with 7 testsSteps to Confirm
nox -s pytest -- tests/lib/test_oauth_util.py::TestCustomPermissionChecker -vnox -s pytest -- tests/lib/test_oauth_util.py -vPre-Merge Checklist
CHANGELOG.mdupdatedmaindowngrade()migration is correct and works