This commit is contained in:
parent
3e4ad4a5da
commit
2e9c3119ce
|
|
@ -40,6 +40,10 @@ NetBox paginates large result sets to reduce the overall response size. The [`MA
|
|||
|
||||
By default, NetBox restricts a GraphQL query to 10 aliases. Consider reducing this number by setting [`GRAPHQL_MAX_ALIASES`](../configuration/graphql-api.md#graphql_max_aliases) to a lower value.
|
||||
|
||||
#### Limit GraphQL Query Depth
|
||||
|
||||
Deeply nested GraphQL queries can impose substantial overhead, consuming undue server resources and increasing response times. Consider setting [`GRAPHQL_MAX_QUERY_DEPTH`](../configuration/graphql-api.md#graphql_max_query_depth) to limit the maximum nesting depth for any GraphQL query.
|
||||
|
||||
#### Designate Isolated Deployments
|
||||
|
||||
If your NetBox installation does not have Internet access, set [`ISOLATED_DEPLOYMENT`](../configuration/system.md#isolated_deployment) to True. This will prevent the application from attempting routine external requests.
|
||||
|
|
|
|||
|
|
@ -25,3 +25,13 @@ Setting this to `False` will disable the GraphQL API.
|
|||
Default: `10`
|
||||
|
||||
The maximum number of queries that a GraphQL API request may contain.
|
||||
|
||||
---
|
||||
|
||||
## GRAPHQL_MAX_QUERY_DEPTH
|
||||
|
||||
!!! note "This parameter was introduced in NetBox v4.6.1."
|
||||
|
||||
Default: `None` (no limit)
|
||||
|
||||
The maximum allowed depth of any GraphQL query. When set to a positive integer, requests containing queries that exceed this depth will be rejected. Leaving this parameter unset (or setting it to `None` or `0`) disables query depth enforcement.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import strawberry
|
||||
from django.conf import settings
|
||||
from strawberry.extensions import MaxAliasesLimiter
|
||||
from strawberry.extensions import MaxAliasesLimiter, QueryDepthLimiter, SchemaExtension
|
||||
from strawberry.schema.config import StrawberryConfig
|
||||
from strawberry_django.optimizer import DjangoOptimizerExtension
|
||||
|
||||
|
|
@ -36,6 +36,17 @@ class Query(
|
|||
pass
|
||||
|
||||
|
||||
def get_schema_extensions() -> list[SchemaExtension]:
|
||||
extensions: list[SchemaExtension] = [
|
||||
DjangoOptimizerExtension(prefetch_custom_queryset=True),
|
||||
MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES),
|
||||
]
|
||||
max_depth = settings.GRAPHQL_MAX_QUERY_DEPTH
|
||||
if max_depth and max_depth > 0:
|
||||
extensions.append(QueryDepthLimiter(max_depth=max_depth))
|
||||
return extensions
|
||||
|
||||
|
||||
schema = strawberry.Schema(
|
||||
query=Query,
|
||||
config=StrawberryConfig(
|
||||
|
|
@ -44,8 +55,5 @@ schema = strawberry.Schema(
|
|||
BigInt: BigIntScalar,
|
||||
},
|
||||
),
|
||||
extensions=[
|
||||
DjangoOptimizerExtension(prefetch_custom_queryset=True),
|
||||
MaxAliasesLimiter(max_alias_count=settings.GRAPHQL_MAX_ALIASES),
|
||||
],
|
||||
extensions=get_schema_extensions(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ FIELD_CHOICES = getattr(configuration, 'FIELD_CHOICES', {})
|
|||
FILE_UPLOAD_MAX_MEMORY_SIZE = getattr(configuration, 'FILE_UPLOAD_MAX_MEMORY_SIZE', 2621440)
|
||||
GRAPHQL_DEFAULT_VERSION = getattr(configuration, 'GRAPHQL_DEFAULT_VERSION', 1)
|
||||
GRAPHQL_MAX_ALIASES = getattr(configuration, 'GRAPHQL_MAX_ALIASES', 10)
|
||||
GRAPHQL_MAX_QUERY_DEPTH = getattr(configuration, 'GRAPHQL_MAX_QUERY_DEPTH', None)
|
||||
HOSTNAME = getattr(configuration, 'HOSTNAME', platform.node())
|
||||
HTTP_PROXIES = getattr(configuration, 'HTTP_PROXIES', {})
|
||||
INTERNAL_IPS = getattr(configuration, 'INTERNAL_IPS', ('127.0.0.1', '::1'))
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import json
|
||||
|
||||
import strawberry
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from rest_framework import status
|
||||
from strawberry.extensions import QueryDepthLimiter
|
||||
from strawberry.schema.config import StrawberryConfig
|
||||
|
||||
from dcim.choices import LocationStatusChoices
|
||||
from dcim.models import Device, DeviceRole, DeviceType, Location, Manufacturer, Site, VirtualChassis
|
||||
from netbox.graphql.scalars import BigInt, BigIntScalar
|
||||
from netbox.graphql.schema import Query, get_schema_extensions
|
||||
from utilities.testing import APITestCase, TestCase, disable_warnings
|
||||
|
||||
|
||||
|
|
@ -20,6 +25,45 @@ class GraphQLTestCase(TestCase):
|
|||
response = self.client.get(url)
|
||||
self.assertHttpStatus(response, 404)
|
||||
|
||||
def test_graphql_max_query_depth_disabled_by_default(self):
|
||||
"""
|
||||
QueryDepthLimiter should not be installed when GRAPHQL_MAX_QUERY_DEPTH is unset.
|
||||
"""
|
||||
self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in get_schema_extensions()))
|
||||
|
||||
@override_settings(GRAPHQL_MAX_QUERY_DEPTH=0)
|
||||
def test_graphql_max_query_depth_disabled_when_zero(self):
|
||||
"""
|
||||
QueryDepthLimiter should not be installed when GRAPHQL_MAX_QUERY_DEPTH is zero.
|
||||
"""
|
||||
self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in get_schema_extensions()))
|
||||
|
||||
@override_settings(GRAPHQL_MAX_QUERY_DEPTH=-1)
|
||||
def test_graphql_max_query_depth_disabled_when_negative(self):
|
||||
"""
|
||||
QueryDepthLimiter should not be installed when GRAPHQL_MAX_QUERY_DEPTH is negative.
|
||||
"""
|
||||
self.assertFalse(any(isinstance(ext, QueryDepthLimiter) for ext in get_schema_extensions()))
|
||||
|
||||
@override_settings(GRAPHQL_MAX_QUERY_DEPTH=3)
|
||||
def test_graphql_max_query_depth_enforced(self):
|
||||
"""
|
||||
Queries exceeding GRAPHQL_MAX_QUERY_DEPTH should be rejected.
|
||||
"""
|
||||
extensions = get_schema_extensions()
|
||||
self.assertTrue(any(isinstance(ext, QueryDepthLimiter) for ext in extensions))
|
||||
|
||||
# Build a temporary schema with the configured extensions and execute a deep query
|
||||
test_schema = strawberry.Schema(
|
||||
query=Query,
|
||||
config=StrawberryConfig(auto_camel_case=False, scalar_map={BigInt: BigIntScalar}),
|
||||
extensions=extensions,
|
||||
)
|
||||
deep_query = '{ site_list { tenant { group { parent { parent { parent { name } } } } } } }'
|
||||
result = test_schema.execute_sync(deep_query)
|
||||
self.assertIsNotNone(result.errors)
|
||||
self.assertIn('exceeds maximum operation depth', str(result.errors[0]))
|
||||
|
||||
@override_settings(LOGIN_REQUIRED=True)
|
||||
def test_graphiql_interface(self):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Reference in New Issue