Introduce GraphQLSchemaCoverageTestCase to verify every model-backed
GraphQL type exposed as a root query field is covered by a test. Add
type_class and graphql_test_exempt attributes to GraphQLTestCase for
explicit type declaration and coverage exclusion. Include
graphql_object_permission_assertions flag to gate permission checks for
types not enforcing object permissions.
Fixes#22089
* #21025: WIP
* Fixes#22357: Remove unused `local_context_data` field from dcim.Module (#22364)
* Add partial index for checking null CC data
* Ensure the data returned by get_config_context() is safe for mutation
* Implement selective backup queryset annotation to avoid n+1 overhead on cold cache
* Fix migration conflict
* Replace MPTT with Ltree per #21418
- Add _validate_json_path(): each __-separated path segment must match
[A-Za-z0-9_][A-Za-z0-9_-]* (allows leading underscores per Jeremy's
suggestion; ORM operator names like 'date'/'regex' are valid JSON keys
and are not blocked — the trailing __ JSONFilter appends makes them
key traversal steps, not ORM transforms)
- Add JSONStringLookup: explicit string-filter type for JSONLookup.
regex/i_regex are included (they offer no additional oracle power
beyond starts_with, which is also present, per Jeremy's observation)
- JSONFilter.filter() validates self.path and returns empty Q() on
invalid input rather than passing untrusted user input to the ORM
- 19 unit tests for path validation and JSONStringLookup field presence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update strawberry-graphql-django to 0.86.1 and remove redundant type
parameters from StrFilterLookup, DateFilterLookup, TimeFilterLookup, and
DatetimeFilterLookup annotations across model-backed GraphQL filters.
Add NetBox-local JSON date, time, and datetime lookup input types to
preserve the previous string-backed JSON filter schema without relying
on deprecated upstream generic lookup annotations. These local types
keep the legacy GraphQL type names and date/time sub-lookup fields
intact.
Fixes#22353
Bulk write operations (create/update/delete a JSON list at a model's list
endpoint) can opt into background processing with the ?background=true query
parameter. The request is validated synchronously and, if accepted, an
AsyncAPIJob is enqueued and a 202 Accepted is returned with the job id and
poll URL; the write is performed later by a worker that re-invokes the same
viewset action, so behavior matches the synchronous path (including
all-or-nothing transaction semantics).
- AsyncAPIJob reconstructs the request in the worker, re-applies object
permissions, runs within the request processors (change logging/events),
and captures the action's response into job.data as {status_code, data}.
- Handled rejections are translated to match the synchronous API: APIException
via handle_exception(), and AbortRequest/ProtectedError/RestrictedError via a
new NetBoxModelViewSet.exception_to_response() helper. These terminate the
job as "failed" (reserving "errored" for unexpected crashes).
- Background processing is refused with 503 when no worker is servicing the
queue, and rejected with 400 when combined with an If-Match precondition
(which cannot be honored once execution is deferred).
- Single-object writes, GET requests, and non-list payloads ignore the
parameter and run synchronously.
exception_to_response() intentionally duplicates the translation logic in
dispatch() rather than dispatch() being refactored to call it; consolidating
the two is left as a follow-up to keep this change off the synchronous hot path.
* Address code review feedback (#21992)
- Carry the request's scheme and host into the background worker so absolute
URLs in the captured job result point at the real server instead of a
hardcoded http://localhost/.
- Emit the same protected-delete warning log in exception_to_response() that
dispatch() produces, restoring application-log parity for background failures.
- Drop the inert `_authenticator = None` assignment: setting request.user
already prevents lazy re-authentication via the public API, and nothing on
the worker's action path reads the authenticator.
- Remove the redundant success-path job.save() (JobRunner.handle() ->
terminate() persists job.data) and hoist the AsyncAPIJob import in mixins.py
to module level (no real import cycle through it).
- Add a test asserting result URLs reflect the request host.
* Fix IPv6 host parsing in background API request reconstruction
Parse the carried host with urlsplit (and pass it verbatim as HTTP_HOST)
instead of host.partition(':'), which split bracketed IPv6 hosts like
[::1]:8443 on their inner colons. Extract request construction into
AsyncAPIJob._build_request and add a test asserting the IPv6 host round-trips.
* Address review feedback (#21992)
- Make the bulk mixins safe to use without BackgroundOperationMixin: guard the
_background_requested / _maybe_background_bulk_create calls with a getattr
fallback so BulkUpdateModelMixin/BulkDestroyModelMixin/SequentialBulkCreatesMixin
retain their standalone behavior in custom viewset composition.
- Add a test covering the background ProtectedError/RestrictedError path: a bulk
delete of a protected object records the same 409 the synchronous API returns
(job failed, status_code 409, object preserved), via exception_to_response().
The method wrote uploaded files to disk via a raw open(), but no code
path reached it. Its only subclass, ScriptFileForm, overrode save() to
write through django-storages and explicitly skipped the base via
super(ManagedFileForm, self).save(). With the override gone, that call
simplifies back to a plain super().save(). A leftover from #18680, which
moved both upload paths onto django-storages but left the form-level
write in place.
Change `get_schema_extensions()` to return extension factories instead
of instances. This defers extension initialization and prevents stale
references to settings captured at import time.
Lambdas capture settings values when extensions are constructed, and
tests now instantiate extensions from factories to verify configuration.
Fixes#22451
The test failures arises from unstable sorting of the usernames
depending on the collation used in the PostgreSQL database used for
testing. When a case-insensitive collation is used 'testuser' is sorted
before 'User*' and because this user has permissions assigned and
additional query is issued resulting in 12 queries. When a
case-sensitive collation is used the sorting is inverted. Because the
'User*' don't have permissions only 11 queries are sent to the database.
Using only testusers with lowercase names enforces stable sorting
across collations.
Replace IPSet-heavy Prefix availability and utilization logic with
indexed host lookups, distinct host counts, and interval-based
availability calculation.
This adds mask-insensitive host-bound filtering for IP addresses and
ranges, moves availability/counting behavior onto QuerySet and model
methods, and uses merged occupied intervals to find available addresses
without materializing large address sets in Python.
Prefix utilization remains on a cheap utilization-only path for list
views, while Prefix detail views can use a shared usage summary when
both utilization and available IP count are needed. Usable IP bounds now
live on the Prefix model, since the logic depends on Prefix-specific
state such as is_pool.
This also adds host expression indexes for IP Ranges, fixes zero-address
preparation, fixes child IP matching across differing mask lengths,
keeps Prefix hierarchy rebuilding scoped to the existing VRF/global API,
and preserves IPRange.first_available_ip as a cached compatibility
wrapper.
Fixes#21870
Follow-up to #22363: align the plugin hook names with the already-renamed
JINJA_FILTERS setting (#22288) and with the rest of the codebase's 'Jinja'
spelling convention.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Token.generate() used Python's random module (Mersenne Twister PRNG).
Mersenne Twister is not a CSPRNG: observing ~624 outputs from the same
worker process allows full state recovery and prediction of subsequent
outputs. Any token minted in the same worker within that window becomes
predictable, including tokens for privileged accounts.
Fix: replace random.choice with secrets.choice. secrets is backed by
os.urandom() / getrandom() which provides OS-level CSPRNG entropy and
is immune to state-recovery attacks.
The import of the now-unused random module is removed.
Regression tests:
- test_generate_uses_csprng: patches secrets.choice with wraps= to
confirm it is called exactly TOKEN_DEFAULT_LENGTH times per generate().
- test_generate_length_parameter: verifies length= is respected and
output is drawn only from TOKEN_CHARSET.
Ref: SR-001 / VM-317 (internal security review, R1-F07 / R3-F1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>