Skip to content

Commit ac1b1bb

Browse files
committed
refactor: consolidate RBAC into dojo/authorization package
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.
1 parent 5178368 commit ac1b1bb

85 files changed

Lines changed: 3015 additions & 2113 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dojo/announcement/views.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@
77
from django.utils.translation import gettext
88
from django.utils.translation import gettext_lazy as _
99

10-
from dojo.authorization.authorization_decorators import (
11-
user_is_configuration_authorized,
12-
)
1310
from dojo.forms import AnnouncementCreateForm, AnnouncementRemoveForm
1411
from dojo.models import Announcement, UserAnnouncement
1512
from dojo.utils import add_breadcrumb
1613

1714
logger = logging.getLogger(__name__)
1815

1916

20-
@user_is_configuration_authorized("dojo.change_announcement")
2117
def configure_announcement(request):
2218
remove = False
2319
if request.method == "GET":

dojo/api_v2/serializers.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@
2626
import dojo.finding.helper as finding_helper
2727
import dojo.risk_acceptance.helper as ra_helper
2828
from dojo.authorization.authorization import user_has_permission
29+
from dojo.authorization.models import (
30+
Dojo_Group_Member,
31+
Global_Role,
32+
Product_Group,
33+
Product_Member,
34+
Product_Type_Group,
35+
Product_Type_Member,
36+
Role,
37+
)
2938
from dojo.authorization.roles_permissions import Permissions
3039
from dojo.celery_dispatch import dojo_dispatch_task
3140
from dojo.endpoint.utils import endpoint_filter, endpoint_meta_import
@@ -61,7 +70,6 @@
6170
Cred_User,
6271
Development_Environment,
6372
Dojo_Group,
64-
Dojo_Group_Member,
6573
Dojo_User,
6674
DojoMeta,
6775
Endpoint,
@@ -75,7 +83,6 @@
7583
Finding_Group,
7684
Finding_Template,
7785
General_Survey,
78-
Global_Role,
7986
Language_Type,
8087
Languages,
8188
Network_Locations,
@@ -86,15 +93,10 @@
8693
Notifications,
8794
Product,
8895
Product_API_Scan_Configuration,
89-
Product_Group,
90-
Product_Member,
9196
Product_Type,
92-
Product_Type_Group,
93-
Product_Type_Member,
9497
Question,
9598
Regulation,
9699
Risk_Acceptance,
97-
Role,
98100
SLA_Configuration,
99101
Sonarqube_Issue,
100102
Sonarqube_Issue_Transition,

dojo/api_v2/views.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,21 @@
4141
mixins as dojo_mixins,
4242
)
4343
from dojo.api_v2 import (
44-
permissions,
4544
prefetch,
4645
serializers,
4746
)
4847
from dojo.api_v2.prefetch.prefetcher import _Prefetcher
48+
from dojo.authorization import api_permissions as permissions
4949
from dojo.authorization.authorization import user_has_permission_or_403
50+
from dojo.authorization.models import (
51+
Dojo_Group_Member,
52+
Global_Role,
53+
Product_Group,
54+
Product_Member,
55+
Product_Type_Group,
56+
Product_Type_Member,
57+
Role,
58+
)
5059
from dojo.authorization.roles_permissions import Permissions
5160
from dojo.celery_dispatch import dojo_dispatch_task
5261
from dojo.cred.queries import get_authorized_cred_mappings
@@ -100,7 +109,6 @@
100109
Cred_User,
101110
Development_Environment,
102111
Dojo_Group,
103-
Dojo_Group_Member,
104112
Dojo_User,
105113
DojoMeta,
106114
Endpoint,
@@ -112,7 +120,6 @@
112120
Finding,
113121
Finding_Template,
114122
General_Survey,
115-
Global_Role,
116123
Language_Type,
117124
Languages,
118125
Network_Locations,
@@ -123,15 +130,10 @@
123130
Notifications,
124131
Product,
125132
Product_API_Scan_Configuration,
126-
Product_Group,
127-
Product_Member,
128133
Product_Type,
129-
Product_Type_Group,
130-
Product_Type_Member,
131134
Question,
132135
Regulation,
133136
Risk_Acceptance,
134-
Role,
135137
SLA_Configuration,
136138
Sonarqube_Issue,
137139
Sonarqube_Issue_Transition,

dojo/asset/api/filters.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
from drf_spectacular.types import OpenApiTypes
44
from drf_spectacular.utils import extend_schema_field
55

6+
from dojo.authorization.models import (
7+
Product_Group,
8+
Product_Member,
9+
)
610
from dojo.filters import (
711
CharFieldFilterANDExpression,
812
CharFieldInFilter,
@@ -16,8 +20,6 @@
1620
from dojo.models import (
1721
Product,
1822
Product_API_Scan_Configuration,
19-
Product_Group,
20-
Product_Member,
2123
)
2224

2325
labels = get_labels()

dojo/asset/api/serializers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33

44
from dojo.api_v2.serializers import ProductMetaSerializer, TagListSerializerField
55
from dojo.authorization.authorization import user_has_permission
6+
from dojo.authorization.models import (
7+
Product_Group,
8+
Product_Member,
9+
)
610
from dojo.authorization.roles_permissions import Permissions
711
from dojo.models import (
812
Dojo_User,
913
Product,
1014
Product_API_Scan_Configuration,
11-
Product_Group,
12-
Product_Member,
1315
)
1416
from dojo.organization.api.serializers import RelatedOrganizationField
1517
from dojo.product.queries import get_authorized_products

dojo/asset/api/views.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from rest_framework.response import Response
77

88
import dojo.api_v2.mixins as dojo_mixins
9-
from dojo.api_v2 import permissions, prefetch
9+
from dojo.api_v2 import prefetch
1010
from dojo.api_v2.serializers import ReportGenerateOptionSerializer, ReportGenerateSerializer
1111
from dojo.api_v2.views import PrefetchDojoModelViewSet, report_generate, schema_with_prefetch
1212
from dojo.asset.api import serializers
@@ -16,12 +16,15 @@
1616
AssetGroupFilterSet,
1717
AssetMemberFilterSet,
1818
)
19+
from dojo.authorization import api_permissions as permissions
20+
from dojo.authorization.models import (
21+
Product_Group,
22+
Product_Member,
23+
)
1924
from dojo.authorization.roles_permissions import Permissions
2025
from dojo.models import (
2126
Product,
2227
Product_API_Scan_Configuration,
23-
Product_Group,
24-
Product_Member,
2528
)
2629
from dojo.product.queries import (
2730
get_authorized_product_api_scan_configurations,

dojo/authorization/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Import query_registrations to trigger RBAC filter registration at startup
2+
from dojo.authorization import query_registrations # noqa: F401
3+
from dojo.authorization.authorization import ( # noqa: F401
4+
user_has_configuration_permission,
5+
user_has_global_permission,
6+
user_has_global_permission_or_403,
7+
user_has_permission,
8+
user_has_permission_or_403,
9+
user_is_superuser_or_global_owner,
10+
)
11+
from dojo.authorization.roles_permissions import Permissions, Roles # noqa: F401
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,3 +1285,41 @@ class UserHasConfigurationPermissionSuperuser(
12851285

12861286
def has_permission(self, request, view):
12871287
return super().has_permission(request, view)
1288+
1289+
1290+
class LocationFindingReferencePermission(permissions.BasePermission):
1291+
def has_permission(self, request, view):
1292+
return check_post_permission(
1293+
request,
1294+
Finding,
1295+
"finding",
1296+
Permissions.Finding_Edit,
1297+
)
1298+
1299+
def has_object_permission(self, request, view, obj):
1300+
return check_object_permission(
1301+
request,
1302+
obj.finding,
1303+
Permissions.Finding_View,
1304+
Permissions.Finding_Edit,
1305+
Permissions.Finding_Edit,
1306+
)
1307+
1308+
1309+
class LocationProductReferencePermission(permissions.BasePermission):
1310+
def has_permission(self, request, view):
1311+
return check_post_permission(
1312+
request,
1313+
Product,
1314+
"product",
1315+
Permissions.Product_Edit,
1316+
)
1317+
1318+
def has_object_permission(self, request, view, obj):
1319+
return check_object_permission(
1320+
request,
1321+
obj.product,
1322+
Permissions.Product_View,
1323+
Permissions.Product_Edit,
1324+
Permissions.Product_Edit,
1325+
)

dojo/authorization/authorization.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
from django.core.exceptions import PermissionDenied
22
from django.db.models import Model, QuerySet
33

4+
from dojo.authorization.models import (
5+
Dojo_Group_Member,
6+
Product_Group,
7+
Product_Member,
8+
Product_Type_Group,
9+
Product_Type_Member,
10+
)
411
from dojo.authorization.roles_permissions import (
512
Permissions,
613
Roles,
@@ -12,7 +19,6 @@
1219
App_Analysis,
1320
Cred_Mapping,
1421
Dojo_Group,
15-
Dojo_Group_Member,
1622
Dojo_User,
1723
Endpoint,
1824
Engagement,
@@ -21,11 +27,7 @@
2127
Languages,
2228
Product,
2329
Product_API_Scan_Configuration,
24-
Product_Group,
25-
Product_Member,
2630
Product_Type,
27-
Product_Type_Group,
28-
Product_Type_Member,
2931
Risk_Acceptance,
3032
Stub_Finding,
3133
Test,

dojo/authorization/middleware.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from django.core.exceptions import PermissionDenied
2+
from django.shortcuts import get_object_or_404
3+
4+
from dojo.authorization.authorization import (
5+
user_has_configuration_permission,
6+
user_has_global_permission_or_403,
7+
user_has_permission_or_403,
8+
)
9+
from dojo.authorization.url_permissions import URL_PERMISSIONS
10+
11+
12+
class AuthorizationMiddleware:
13+
def __init__(self, get_response):
14+
self.get_response = get_response
15+
16+
def __call__(self, request):
17+
return self.get_response(request)
18+
19+
def process_view(self, request, view_func, view_args, view_kwargs):
20+
# Skip API paths -- DRF has its own permission classes
21+
if request.path.startswith("/api/"):
22+
return
23+
24+
resolver_match = request.resolver_match
25+
if resolver_match is None:
26+
return
27+
28+
url_name = resolver_match.url_name
29+
checks = URL_PERMISSIONS.get(url_name)
30+
if not checks:
31+
return
32+
33+
for check in checks:
34+
check_type = check[0]
35+
if check_type == "global":
36+
_, permission = check
37+
user_has_global_permission_or_403(request.user, permission)
38+
elif check_type == "config":
39+
_, permission = check
40+
if not user_has_configuration_permission(request.user, permission):
41+
raise PermissionDenied
42+
elif check_type == "object":
43+
_, model, permission, arg_name = check
44+
lookup_value = view_kwargs.get(arg_name)
45+
if lookup_value is None:
46+
continue # kwarg not present, skip this check
47+
obj = get_object_or_404(model, pk=lookup_value)
48+
user_has_permission_or_403(request.user, obj, permission)
49+
50+
return

0 commit comments

Comments
 (0)