Skip to content

Commit fb959be

Browse files
authorizations: cache results per requests if possible
1 parent daa4be7 commit fb959be

18 files changed

Lines changed: 486 additions & 177 deletions

File tree

dojo/cred/queries.py

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

44
from dojo.authorization.authorization import get_roles_for_permission, user_has_global_permission
55
from dojo.models import Cred_Mapping, Product_Group, Product_Member, Product_Type_Group, Product_Type_Member
6+
from dojo.request_cache import cache_for_request
67

78

8-
def get_authorized_cred_mappings(permission, queryset=None):
9+
# Cached: all parameters are hashable, no dynamic queryset filtering
10+
@cache_for_request
11+
def get_authorized_cred_mappings(permission):
12+
"""Cached - returns all cred mappings the user is authorized to see."""
913
user = get_current_user()
1014

1115
if user is None:
1216
return Cred_Mapping.objects.none()
1317

14-
cred_mappings = Cred_Mapping.objects.all().order_by("id") if queryset is None else queryset
18+
cred_mappings = Cred_Mapping.objects.all().order_by("id")
1519

1620
if user.is_superuser:
1721
return cred_mappings
@@ -45,3 +49,44 @@ def get_authorized_cred_mappings(permission, queryset=None):
4549
| Q(product__prod_type_id__in=Subquery(authorized_product_type_groups))
4650
| Q(product_id__in=Subquery(authorized_product_groups)),
4751
)
52+
53+
54+
def get_authorized_cred_mappings_for_queryset(permission, queryset):
55+
"""Filters a provided queryset for authorization. Not cached due to dynamic queryset parameter."""
56+
user = get_current_user()
57+
58+
if user is None:
59+
return Cred_Mapping.objects.none()
60+
61+
if user.is_superuser:
62+
return queryset
63+
64+
if user_has_global_permission(user, permission):
65+
return queryset
66+
67+
roles = get_roles_for_permission(permission)
68+
69+
# Get authorized product/product_type IDs via subqueries
70+
authorized_product_type_roles = Product_Type_Member.objects.filter(
71+
user=user, role__in=roles,
72+
).values("product_type_id")
73+
74+
authorized_product_roles = Product_Member.objects.filter(
75+
user=user, role__in=roles,
76+
).values("product_id")
77+
78+
authorized_product_type_groups = Product_Type_Group.objects.filter(
79+
group__users=user, role__in=roles,
80+
).values("product_type_id")
81+
82+
authorized_product_groups = Product_Group.objects.filter(
83+
group__users=user, role__in=roles,
84+
).values("product_id")
85+
86+
# Filter using IN with Subquery - no annotations needed
87+
return queryset.filter(
88+
Q(product__prod_type_id__in=Subquery(authorized_product_type_roles))
89+
| Q(product_id__in=Subquery(authorized_product_roles))
90+
| Q(product__prod_type_id__in=Subquery(authorized_product_type_groups))
91+
| Q(product_id__in=Subquery(authorized_product_groups)),
92+
)

dojo/endpoint/queries.py

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,20 @@
1010
Product_Type_Group,
1111
Product_Type_Member,
1212
)
13+
from dojo.request_cache import cache_for_request
1314

1415

15-
def get_authorized_endpoints(permission, queryset=None, user=None):
16-
16+
# Cached: all parameters are hashable, no dynamic queryset filtering
17+
@cache_for_request
18+
def get_authorized_endpoints(permission, user=None):
19+
"""Cached - returns all endpoints the user is authorized to see."""
1720
if user is None:
1821
user = get_current_user()
1922

2023
if user is None:
2124
return Endpoint.objects.none()
2225

23-
endpoints = Endpoint.objects.all().order_by("id") if queryset is None else queryset
26+
endpoints = Endpoint.objects.all().order_by("id")
2427

2528
if user.is_superuser:
2629
return endpoints
@@ -56,15 +59,59 @@ def get_authorized_endpoints(permission, queryset=None, user=None):
5659
)
5760

5861

59-
def get_authorized_endpoint_status(permission, queryset=None, user=None):
62+
def get_authorized_endpoints_for_queryset(permission, queryset, user=None):
63+
"""Filters a provided queryset for authorization. Not cached due to dynamic queryset parameter."""
64+
if user is None:
65+
user = get_current_user()
66+
67+
if user is None:
68+
return Endpoint.objects.none()
69+
70+
if user.is_superuser:
71+
return queryset
72+
73+
if user_has_global_permission(user, permission):
74+
return queryset
75+
76+
roles = get_roles_for_permission(permission)
77+
78+
# Get authorized product/product_type IDs via subqueries
79+
authorized_product_type_roles = Product_Type_Member.objects.filter(
80+
user=user, role__in=roles,
81+
).values("product_type_id")
82+
83+
authorized_product_roles = Product_Member.objects.filter(
84+
user=user, role__in=roles,
85+
).values("product_id")
86+
87+
authorized_product_type_groups = Product_Type_Group.objects.filter(
88+
group__users=user, role__in=roles,
89+
).values("product_type_id")
90+
91+
authorized_product_groups = Product_Group.objects.filter(
92+
group__users=user, role__in=roles,
93+
).values("product_id")
94+
95+
# Filter using IN with Subquery - no annotations needed
96+
return queryset.filter(
97+
Q(product__prod_type_id__in=Subquery(authorized_product_type_roles))
98+
| Q(product_id__in=Subquery(authorized_product_roles))
99+
| Q(product__prod_type_id__in=Subquery(authorized_product_type_groups))
100+
| Q(product_id__in=Subquery(authorized_product_groups)),
101+
)
102+
60103

104+
# Cached: all parameters are hashable, no dynamic queryset filtering
105+
@cache_for_request
106+
def get_authorized_endpoint_status(permission, user=None):
107+
"""Cached - returns all endpoint statuses the user is authorized to see."""
61108
if user is None:
62109
user = get_current_user()
63110

64111
if user is None:
65112
return Endpoint_Status.objects.none()
66113

67-
endpoint_status = Endpoint_Status.objects.all().order_by("id") if queryset is None else queryset
114+
endpoint_status = Endpoint_Status.objects.all().order_by("id")
68115

69116
if user.is_superuser:
70117
return endpoint_status
@@ -98,3 +145,45 @@ def get_authorized_endpoint_status(permission, queryset=None, user=None):
98145
| Q(endpoint__product__prod_type_id__in=Subquery(authorized_product_type_groups))
99146
| Q(endpoint__product_id__in=Subquery(authorized_product_groups)),
100147
)
148+
149+
150+
def get_authorized_endpoint_status_for_queryset(permission, queryset, user=None):
151+
"""Filters a provided queryset for authorization. Not cached due to dynamic queryset parameter."""
152+
if user is None:
153+
user = get_current_user()
154+
155+
if user is None:
156+
return Endpoint_Status.objects.none()
157+
158+
if user.is_superuser:
159+
return queryset
160+
161+
if user_has_global_permission(user, permission):
162+
return queryset
163+
164+
roles = get_roles_for_permission(permission)
165+
166+
# Get authorized product/product_type IDs via subqueries
167+
authorized_product_type_roles = Product_Type_Member.objects.filter(
168+
user=user, role__in=roles,
169+
).values("product_type_id")
170+
171+
authorized_product_roles = Product_Member.objects.filter(
172+
user=user, role__in=roles,
173+
).values("product_id")
174+
175+
authorized_product_type_groups = Product_Type_Group.objects.filter(
176+
group__users=user, role__in=roles,
177+
).values("product_type_id")
178+
179+
authorized_product_groups = Product_Group.objects.filter(
180+
group__users=user, role__in=roles,
181+
).values("product_id")
182+
183+
# Filter using IN with Subquery - no annotations needed
184+
return queryset.filter(
185+
Q(endpoint__product__prod_type_id__in=Subquery(authorized_product_type_roles))
186+
| Q(endpoint__product_id__in=Subquery(authorized_product_roles))
187+
| Q(endpoint__product__prod_type_id__in=Subquery(authorized_product_type_groups))
188+
| Q(endpoint__product_id__in=Subquery(authorized_product_groups)),
189+
)

dojo/endpoint/views.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from dojo.authorization.authorization import user_has_permission_or_403
1919
from dojo.authorization.authorization_decorators import user_is_authorized
2020
from dojo.authorization.roles_permissions import Permissions
21-
from dojo.endpoint.queries import get_authorized_endpoints
21+
from dojo.endpoint.queries import get_authorized_endpoints_for_queryset
2222
from dojo.endpoint.utils import clean_hosts_run, endpoint_meta_import
2323
from dojo.filters import EndpointFilter, EndpointFilterWithoutObjectLookups
2424
from dojo.forms import AddEndpointForm, DeleteEndpointForm, DojoMetaDataForm, EditEndpointForm, ImportEndpointMetaForm
@@ -52,7 +52,7 @@ def process_endpoints_view(request, *, host_view=False, vulnerable=False):
5252
endpoints = Endpoint.objects.all()
5353

5454
endpoints = endpoints.prefetch_related("product", "product__tags", "tags").distinct()
55-
endpoints = get_authorized_endpoints(Permissions.Endpoint_View, endpoints, request.user)
55+
endpoints = get_authorized_endpoints_for_queryset(Permissions.Endpoint_View, endpoints, request.user)
5656
filter_string_matching = get_system_setting("filter_string_matching", False)
5757
filter_class = EndpointFilterWithoutObjectLookups if filter_string_matching else EndpointFilter
5858
if host_view:
@@ -365,7 +365,7 @@ def endpoint_bulk_update_all(request, pid=None):
365365
product = get_object_or_404(Product, id=pid)
366366
user_has_permission_or_403(request.user, product, Permissions.Endpoint_Delete)
367367

368-
endpoints = get_authorized_endpoints(Permissions.Endpoint_Delete, endpoints, request.user)
368+
endpoints = get_authorized_endpoints_for_queryset(Permissions.Endpoint_Delete, endpoints, request.user)
369369

370370
skipped_endpoint_count = total_endpoint_count - endpoints.count()
371371
deleted_endpoint_count = endpoints.count()
@@ -389,7 +389,7 @@ def endpoint_bulk_update_all(request, pid=None):
389389
product = get_object_or_404(Product, id=pid)
390390
user_has_permission_or_403(request.user, product, Permissions.Finding_Edit)
391391

392-
endpoints = get_authorized_endpoints(Permissions.Endpoint_Edit, endpoints, request.user)
392+
endpoints = get_authorized_endpoints_for_queryset(Permissions.Endpoint_Edit, endpoints, request.user)
393393

394394
skipped_endpoint_count = total_endpoint_count - endpoints.count()
395395
updated_endpoint_count = endpoints.count()

dojo/engagement/queries.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
from dojo.authorization.authorization import get_roles_for_permission, user_has_global_permission
55
from dojo.models import Engagement, Product_Group, Product_Member, Product_Type_Group, Product_Type_Member
6+
from dojo.request_cache import cache_for_request
67

78

9+
# Cached: all parameters are hashable, no dynamic queryset filtering
10+
@cache_for_request
811
def get_authorized_engagements(permission):
912
user = get_current_user()
1013

dojo/filters.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
# from tagulous.forms import TagWidget
3939
# import tagulous
4040
from dojo.authorization.roles_permissions import Permissions
41-
from dojo.endpoint.queries import get_authorized_endpoints
41+
from dojo.endpoint.queries import get_authorized_endpoints_for_queryset
4242
from dojo.engagement.queries import get_authorized_engagements
4343
from dojo.finding.helper import (
4444
ACCEPTED_FINDINGS_QUERY,
@@ -52,8 +52,8 @@
5252
VERIFIED_FINDINGS_QUERY,
5353
WAS_ACCEPTED_FINDINGS_QUERY,
5454
)
55-
from dojo.finding.queries import get_authorized_findings
56-
from dojo.finding_group.queries import get_authorized_finding_groups
55+
from dojo.finding.queries import get_authorized_findings_for_queryset
56+
from dojo.finding_group.queries import get_authorized_finding_groups_for_queryset
5757
from dojo.labels import get_labels
5858
from dojo.models import (
5959
EFFORT_FOR_FIXING_CHOICES,
@@ -2098,7 +2098,7 @@ def set_related_object_fields(self, *args: list, **kwargs: dict):
20982098
if self.form.fields.get("test__engagement__product"):
20992099
self.form.fields["test__engagement__product"].queryset = get_authorized_products(Permissions.Product_View)
21002100
if self.form.fields.get("finding_group", None):
2101-
self.form.fields["finding_group"].queryset = get_authorized_finding_groups(Permissions.Finding_Group_View, queryset=finding_group_query)
2101+
self.form.fields["finding_group"].queryset = get_authorized_finding_groups_for_queryset(Permissions.Finding_Group_View, finding_group_query)
21022102
self.form.fields["reporter"].queryset = get_authorized_users(Permissions.Finding_View)
21032103
self.form.fields["reviewers"].queryset = self.form.fields["reporter"].queryset
21042104

@@ -2205,7 +2205,7 @@ def set_hash_codes(self, *args: list, **kwargs: dict):
22052205

22062206
def filter_queryset(self, *args: list, **kwargs: dict):
22072207
queryset = super().filter_queryset(*args, **kwargs)
2208-
queryset = get_authorized_findings(Permissions.Finding_View, queryset, self.user)
2208+
queryset = get_authorized_findings_for_queryset(Permissions.Finding_View, queryset, self.user)
22092209
return queryset.exclude(pk=self.finding.pk)
22102210

22112211

@@ -2751,7 +2751,7 @@ def __init__(self, *args, **kwargs):
27512751
@property
27522752
def qs(self):
27532753
parent = super().qs
2754-
return get_authorized_endpoints(Permissions.Endpoint_View, parent)
2754+
return get_authorized_endpoints_for_queryset(Permissions.Endpoint_View, parent)
27552755

27562756
class Meta:
27572757
model = Endpoint
@@ -2892,7 +2892,7 @@ def __init__(self, *args, **kwargs):
28922892
@property
28932893
def qs(self):
28942894
parent = super().qs
2895-
return get_authorized_endpoints(Permissions.Endpoint_View, parent)
2895+
return get_authorized_endpoints_for_queryset(Permissions.Endpoint_View, parent)
28962896

28972897
class Meta:
28982898
model = Endpoint
@@ -3240,7 +3240,7 @@ def manage_kwargs(self, kwargs):
32403240
@property
32413241
def qs(self):
32423242
parent = super().qs
3243-
return get_authorized_findings(Permissions.Finding_View, parent)
3243+
return get_authorized_findings_for_queryset(Permissions.Finding_View, parent)
32443244

32453245

32463246
class ReportFindingFilter(ReportFindingFilterHelper, FindingTagFilter):
@@ -3260,7 +3260,7 @@ def __init__(self, *args, **kwargs):
32603260
# duplicate_finding queryset needs to restricted in line with permissions
32613261
# and inline with report scope to avoid a dropdown with 100K entries
32623262
duplicate_finding_query_set = self.form.fields["duplicate_finding"].queryset
3263-
duplicate_finding_query_set = get_authorized_findings(Permissions.Finding_View, duplicate_finding_query_set)
3263+
duplicate_finding_query_set = get_authorized_findings_for_queryset(Permissions.Finding_View, duplicate_finding_query_set)
32643264

32653265
if self.test:
32663266
duplicate_finding_query_set = duplicate_finding_query_set.filter(test=self.test)

0 commit comments

Comments
 (0)