refactor: consolidate RBAC into dojo/authorization package#14764
refactor: consolidate RBAC into dojo/authorization package#14764Maffooch wants to merge 7 commits intoDefectDojo:devfrom
Conversation
Move every RBAC / authorization concern into a single dojo/authorization/ package. Before this change authorization code lived in seven different places: dojo/models.py (RBAC models), 14 per-app queries.py files (get_authorized_*), every view file (@user_is_authorized decorators), dojo/api_v2/permissions.py, dojo/location/api/permissions.py, and dojo/templatetags/authorization_tags.py. Changes - Move 7 RBAC models (Role, Global_Role, Dojo_Group_Member, Product_Member, Product_Group, Product_Type_Member, Product_Type_Group) from dojo/models.py to dojo/authorization/models.py. app_label='dojo' is preserved so no migrations are needed; ~47 import sites are updated. - Merge dojo/api_v2/permissions.py and dojo/location/api/permissions.py into dojo/authorization/api_permissions.py. - Extract template-tag logic from dojo/templatetags/authorization_tags.py into dojo/authorization/template_filters.py; the templatetags module becomes a thin registration proxy. - Add dojo/authorization/query_filters.py (registry) and dojo/authorization/query_registrations.py (~1.9k lines of RBAC filter logic extracted from 14 per-app queries.py files). Each get_authorized_* becomes a thin wrapper that defers to the registry and falls back to unfiltered querysets when no RBAC backend is registered. - Add dojo/authorization/url_permissions.py mapping ~198 URL names to permission checks plus dojo/authorization/middleware.py with AuthorizationMiddleware enforcing them via process_view. Removes @user_is_authorized, @user_has_global_permission, and @user_is_configuration_authorized from 26 view files. - Update dojo/authorization/__init__.py exports and trigger query-filter registration at app startup. Behavior is unchanged: authorization checks for the ~198 mapped URLs now run in middleware (process_view) instead of view bodies, but produce the same allow/deny outcome. Non-RBAC deployments keep working because get_authorized_* falls back to unfiltered querysets. Tests: unittests/test_permissions_audit.py exercises the URL-permission map for completeness; existing API/UI suites pass.
valentijnscholten
left a comment
There was a problem hiding this comment.
No more per-view decorators — one URL-permission map replaces ~200 decorator lines scattered across 26 view files.
I don't think I like this. It requires jumping back and forth to the mapping to find what mapping is in place and it increases the chance someone forgets a mapping altogheter or leaves an old mapping behind. Why is this being done?
|
The hope is to make permissions easier the manage in a single location. This will likely make reviews easier as well since we only have to look out for a single point of failure for authorization rather than having it spread out |
|
This pull request has conflicts, please resolve those before we can evaluate the pull request. |
mtesauro
left a comment
There was a problem hiding this comment.
Approved
(assumes the merge conflict is fixed first)
# Conflicts: # dojo/forms.py # dojo/github/ui/views.py
|
Conflicts have been resolved. A maintainer will review the pull request shortly. |
The merge from upstream brought in dojo/notifications/api/views.py referencing dojo.api_v2.permissions, which this branch already moved to dojo.authorization.api_permissions. The stale import broke URLConf resolution, failing every test that boots the app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
This pull request introduces several medium-severity authorization fallbacks that can fail open and expose engagements, findings, credentials, and finding groups if filter registration is missing, plus multiple high-severity IDOR risks where views fetch objects directly from user-controlled IDs without visible in-view permission checks. The proposed middleware/URL-permission mitigations are not shown as enabled in Django settings, so the scanner cannot confirm they actually protect these endpoints.
🟠 Potential IDOR Vulnerability in
|
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The endpoint report views retrieve an Endpoint directly using the user-controlled eid route parameter. The shown view code does not scope the lookup to the current user and does not perform an in-function object permission check before generating the report. Although the PR adds a URL-permission middleware mapping for these routes, the provided full patch does not show that new middleware being installed in Django's MIDDLEWARE setting, so that mitigation is not demonstrably active. |
django-DefectDojo/dojo/endpoint/views.py
Lines 501 to 504 in f82779e
🟠 Potential IDOR Vulnerability in dojo/engagement/views.py (drs_fa0aa793)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The view retrieves an Engagement using the user-controlled eid path parameter via Engagement.objects.get(id=eid) without a user-scoped query or an in-view permission check. Although the patch adds a centralized URL permission mapping for add_tests, the provided full PR patch does not show the new AuthorizationMiddleware being registered/enabled, so that mitigation is not proven to execute. |
django-DefectDojo/dojo/engagement/views.py
Lines 634 to 635 in f82779e
🟠 Potential IDOR Vulnerability in dojo/benchmark/views.py (drs_63e7210f)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The code retrieves a Product record directly by the user-supplied pid parameter using get_object_or_404(Product, id=pid). Although the patch adds a centralized authorization middleware and a URL permission entry for delete_product_benchmark, the provided full patch does not show that the new middleware is registered/enabled in Django settings, so the object-level permission check is not proven to execute before this view. |
django-DefectDojo/dojo/benchmark/views.py
Lines 288 to 289 in f82779e
🟠 Potential IDOR Vulnerability in dojo/endpoint/views.py (drs_711dfe2c)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The view fetches a Product using the user-controlled pid route parameter without an authorization-scoped query or an in-view permission check. Although the patch adds a URL_PERMISSIONS entry and AuthorizationMiddleware, the provided full patch does not show that this middleware is registered/enabled, so the added mapping is not evidence of enforcement for this request. |
django-DefectDojo/dojo/endpoint/views.py
Lines 467 to 468 in f82779e
🟠 Potential IDOR Vulnerability in dojo/finding/views.py (drs_c59c4d11)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The view uses the URL-controlled fid directly to fetch a Finding by primary key. Although the patch introduces a central URL permission map and middleware intended to authorize copy_finding, the provided full patch does not show that the new AuthorizationMiddleware is registered in Django's middleware stack, so that mitigation is not proven to execute before this view. No direct ownership-scoped query or in-function permission check is visible for copy_finding. |
django-DefectDojo/dojo/finding/views.py
Lines 1422 to 1423 in f82779e
🟠 Potential IDOR Vulnerability in dojo/engagement/views.py (drs_5072d780)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The view fetches an Engagement directly by the user-controlled eid URL parameter without any ownership-scoped query or in-view permission check. Although the patch adds a URL_PERMISSIONS entry and AuthorizationMiddleware that would check copy_engagement, the provided full PR patch does not show that new middleware being registered/enabled in Django settings, so there is no clear active authorization mitigation for this direct object lookup. |
django-DefectDojo/dojo/engagement/views.py
Lines 390 to 391 in f82779e
🟠 Potential IDOR Vulnerability in dojo/finding/views.py (drs_32bd50eb)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The view retrieves a Finding using the user-controlled fid parameter and the only apparent authorization mitigation is a newly added URL-permission middleware that is not shown as registered in Django settings in the provided full patch, so the object-level permission check would not execute before the view. |
django-DefectDojo/dojo/finding/views.py
Lines 1376 to 1377 in f82779e
🟠 Potential IDOR Vulnerability in dojo/finding/views.py (drs_b7f5be3e)
| Vulnerability | Potential IDOR Vulnerability |
|---|---|
| Description | The view fetches a Finding directly from the user-controlled fid path parameter and the patch removes/does not show an effective per-object authorization check in the view. Although the PR adds a URL permission mapping and AuthorizationMiddleware, the provided full patch does not register that middleware in Django's MIDDLEWARE settings, so the central object check is not shown to execute. |
django-DefectDojo/dojo/finding/views.py
Line 3498 in f82779e
🟡 Potential authorization bypass due to fallback queryset return in engagement queries in dojo/engagement/queries.py (drs_330bba8a)
| Vulnerability | Potential authorization bypass due to fallback queryset return in engagement queries |
|---|---|
| Description | The engagement query helper get_authorized_engagements defaults to returning Engagement.objects.all() if no authorization filter is registered for the key 'engagement.get_authorized_engagements'. While the system is designed to initialize authorization filters at startup by importing dojo.authorization.query_registrations in dojo/authorization/__init__.py, this relies on correct import sequencing. If the import system fails or circular dependencies occur (causing the registration to be bypassed), engagement records would be globally exposed to any user, bypassing all product-level and group-level authorization scopes. |
🟡 Insecure Authorization Fallback in dojo/finding/queries.py (drs_f5050390)
| Vulnerability | Insecure Authorization Fallback |
|---|---|
| Description | The authorization query helpers in dojo/finding/queries.py employ an insecure-by-default design pattern. When an authorization filter implementation is not found in the _AUTH_FILTER_REGISTRY, the helpers fall back to returning the full objects.all() queryset. This design assumes that the registry will always be populated, creating a risk where any failure in the registration process—such as a circular import, an uninitialized module, or a race condition—results in an immediate, global authorization bypass. Given that these helpers serve as the primary access control mechanism for all finding-related data in the application, including sensitive vulnerability information, a broad fallback is dangerous. Furthermore, multiple views rely on these helpers to enforce data scoping, and while some downstream views implement additional object-level checks, many rely solely on the initial queryset filtering provided by these helpers. |
django-DefectDojo/dojo/finding/queries.py
Lines 34 to 37 in f82779e
🟡 Potential authorization bypass via fallback queryset return in credential queries in dojo/cred/queries.py (drs_a13aca38)
| Vulnerability | Potential authorization bypass via fallback queryset return in credential queries |
|---|---|
| Description | The vulnerability involves credential query helper functions (get_authorized_cred_mappings and get_authorized_cred_mappings_for_queryset) that default to returning the full, unfiltered querysets if the authorization filter implementation is not registered. While the dojo/authorization/__init__.py attempts to trigger registration of filters at startup, relying on this side-effect for security-critical authorization introduces a race condition or failure point. If get_auth_filter returns None (for example, due to an import failure or improper execution context), the application fails open, returning all credentials to the caller. Given that credential mapping records contain sensitive information (like usernames and environment associations), exposing them to unauthorized users is a significant security risk. |
django-DefectDojo/dojo/cred/queries.py
Lines 14 to 16 in f82779e
🟡 Authorization Bypass in Finding Group Queries in dojo/finding_group/queries.py (drs_bdfdbd4a)
| Vulnerability | Authorization Bypass in Finding Group Queries |
|---|---|
| Description | The query helpers in dojo/finding_group/queries.py fallback to returning all Finding_Group objects (Finding_Group.objects.all()) or an unmodified queryset if the authorization filter implementation is not retrieved. While dojo/authorization/__init__.py imports query_registrations to ensure registration at startup, if for any reason the registry lookup fails or is uninitialized (e.g., due to circular imports or improper initialization), the system fails open by granting full access. This bypasses the intended Role-Based Access Control (RBAC). |
django-DefectDojo/dojo/finding_group/queries.py
Lines 1 to 23 in f82779e
Comment to provide feedback on these findings.
Report false positive: @dryrunsecurity fp [FINDING ID] [FEEDBACK]
Report low-impact: @dryrunsecurity nit [FINDING ID] [FEEDBACK]
Example: @dryrunsecurity fp drs_90eda195 This code is not user-facing
All finding details can be found in the DryRun Security Dashboard.
# Conflicts: # unittests/test_remote_user.py
Description
Consolidate every RBAC / authorization concern into a single
dojo/authorization/package. Before this PR, authorization code lived in seven different places:dojo/models.py(RBAC models), 14 per-appqueries.pyfiles (get_authorized_*), every view file (@user_is_authorizeddecorators),dojo/api_v2/permissions.py,dojo/location/api/permissions.py, anddojo/templatetags/authorization_tags.py. After this PR, it all lives indojo/authorization/.Net diff: 85 files changed, +3015 / −2113.
What moves into
dojo/authorization/models.py— 7 RBAC models (Role,Global_Role,Dojo_Group_Member,Product_Member,Product_Group,Product_Type_Member,Product_Type_Group) extracted fromdojo/models.py.app_label='dojo'is preserved so no migrations are needed; ~47 import sites are updated.api_permissions.py—dojo/api_v2/permissions.pyanddojo/location/api/permissions.pymerged into one module; originals deleted; consumers and tests rewritten.template_filters.py— filter functions extracted fromdojo/templatetags/authorization_tags.py. The templatetags module becomes a thin registration proxy.query_filters.py+query_registrations.py— registry pattern (~1.9k lines of RBAC filter logic) extracted from 14 per-appqueries.pyfiles. Eachget_authorized_*becomes a thin wrapper that defers to the registry and falls back to unfiltered querysets when no RBAC backend is registered.url_permissions.py+middleware.py— map ~198 URL names to permission checks and enforce them viaAuthorizationMiddleware.process_view. Removes@user_is_authorized,@user_has_global_permission, and@user_is_configuration_authorizedfrom 26 view files.__init__.py— exports the public surface and triggers query-filter registration at app startup.Why
dojo.modelsfrom authorization — RBAC models live in their own module, removing circular-import pressure.get_authorized_*falls back to unfiltered querysets when no RBAC backend is registered, so non-RBAC deployments keep working.Behavioral changes
None intended. Authorization checks for the ~198 mapped URLs now run in middleware (
process_view) instead of inside view bodies, but produce the same allow/deny outcome.Test results
unittests/test_permissions_audit.pyexercises the URL-permission map for completeness.