Procházet zdrojové kódy

Closes #22615: Remove legacy request_id and username parameters from webhook context

Jeremy Stretch před 17 hodinami
rodič
revize
80c81230a4

+ 1 - 10
docs/integrations/webhooks.md

@@ -17,7 +17,7 @@ For example, you might create a NetBox webhook to [trigger a Slack message](http
 * HTTP method: `POST`
 * URL: Slack incoming webhook URL
 * HTTP content type: `application/json`
-* Body template: `{"text": "IP address {{ data['address'] }} was created by {{ username }}!"}`
+* Body template: `{"text": "IP address {{ data['address'] }} was created by {{ request.user }}!"}`
 
 ### Available Context
 
@@ -35,13 +35,6 @@ The following data is available as context for Jinja2 templates:
     * `request.user` - The name of the authenticated user who made the request (if available)
 * `data` - A detailed representation of the object in its current state. This is typically equivalent to the model's representation in NetBox's REST API.
 * `snapshots` - Minimal "snapshots" of the object state both before and after the change was made; provided as a dictionary with keys named `prechange` and `postchange`. These are not as extensive as the fully serialized representation, but contain enough information to convey what has changed.
-* ⚠️ `request_id` - The unique request ID. This may be used to correlate multiple changes associated with a single request.
-* ⚠️ `username` - The name of the user account associated with the change.
-
-!!! warning "Deprecation of legacy keys"
-    The `request_id` and `username` keys in the webhook payload above are deprecated and should no longer be used. Support for them will be removed in NetBox v4.7.0.
-
-    Use `request.user` and `request.id` from the `request` object included in the callback context instead.
 
 ### Default Request Body
 
@@ -52,8 +45,6 @@ If no body template is specified, the request body will be populated with a JSON
     "event": "created",
     "timestamp": "2026-03-06T15:11:23.503186+00:00",
     "object_type": "dcim.site",
-    "username": "jstretch",
-    "request_id": "17af32f0-852a-46ca-a7d4-33ecd0c13de6",
     "data": {
         "id": 4,
         "url": "/api/dcim/sites/4/",

+ 3 - 4
docs/models/extras/webhook.md

@@ -84,10 +84,9 @@ The following context variables are available to the text and link templates.
 | `event`       | The event type (`create`, `update`, or `delete`)     |
 | `timestamp`   | The time at which the event occurred                 |
 | `object_type` | The type of object impacted (`app_label.model_name`) |
-| `username`    | The name of the user associated with the change      |
-| `request_id`  | The unique request ID                                |
 | `data`        | A complete serialized representation of the object   |
 | `snapshots`   | Pre- and post-change snapshots of the object         |
+| `request`     | Data about the triggering request (if available)     |
 
-!!! warning "Deprecation of legacy fields"
-    The `request_id` and `username` fields in the webhook payload above are deprecated and should no longer be used. Support for them will be removed in NetBox v4.7.0. Use `request.user` and `request.id` from the `request` object included in the callback context instead. (Note that `request` is populated in the context only when the webhook is associated with a triggering request.)
+!!! note
+    The `request` variable is populated in the context only when the webhook is associated with a triggering request. It exposes `request.id` (the unique request ID) and `request.user` (the name of the user associated with the change), among other attributes.

+ 0 - 7
docs/plugins/development/webhooks.md

@@ -29,8 +29,6 @@ The resulting webhook payload will look like the following:
     "event": "updated",
     "timestamp": "2025-08-07T14:24:30.627321+00:00",
     "object_type": "dcim.site",
-    "username": "admin",
-    "request_id": "49e3e39e-7333-4b9c-a9af-19f0dc1e7dc9",
     "data": {
         "id": 2,
         "url": "/api/dcim/sites/2/",
@@ -44,11 +42,6 @@ The resulting webhook payload will look like the following:
 }
 ```
 
-!!! warning "Deprecation of legacy keys"
-    The `request_id` and `username` keys in the webhook payload above are deprecated and should no longer be used. Support for them will be removed in NetBox v4.7.0.
-
-    Use `request.user` and `request.id` from the `request` object included in the callback context instead.
-
 !!! note "Consider namespacing webhook data"
     The data returned from all webhook callbacks will be compiled into a single `context` dictionary. Any existing keys within this dictionary will be overwritten by subsequent callbacks which include those keys. To avoid collisions with webhook data provided by other plugins, consider namespacing your plugin's data within a nested dictionary as such:
     

+ 5 - 19
netbox/extras/events.py

@@ -154,9 +154,6 @@ def enqueue_event(queue, instance, request, event_type):
             snapshots=get_snapshots(instance, event_type),
             request=request,
             user=request.user,
-            # Legacy request attributes for backward compatibility
-            username=request.user.username,  # DEPRECATED, will be removed in NetBox v4.7.0
-            request_id=request.id,           # DEPRECATED, will be removed in NetBox v4.7.0
         )
 
     # For delete events, eagerly serialize the payload before the row is gone.
@@ -170,15 +167,11 @@ def process_event_rules(event_rules, object_type, event):
     Process a list of EventRules against an event.
 
     Notes on event sources:
-    - Object change events (created/updated/deleted) are enqueued via
-      enqueue_event() during an HTTP request.
-      These events include a request object and legacy request
-      attributes (e.g. username, request_id) for backward compatibility.
-    - Job lifecycle events (JOB_STARTED/JOB_COMPLETED) are emitted by
-      job_start/job_end signal handlers and may not include a request
-      context.
-      Consumers must not assume that fields like `username` are always
-      present.
+    - Object change events (created/updated/deleted) are enqueued via enqueue_event()
+      during an HTTP request. These events include a request object.
+    - Job lifecycle events (JOB_STARTED/JOB_COMPLETED) are emitted by job_start/job_end
+      signal handlers and may not include a request context. Consumers must not assume
+      that a request is always present.
     """
 
     for event_rule in event_rules:
@@ -215,12 +208,6 @@ def process_event_rules(event_rules, object_type, event):
             queue_name = get_config().QUEUE_MAPPINGS.get('webhook', RQ_QUEUE_DEFAULT)
             rq_queue = get_queue(queue_name)
 
-            # For job lifecycle events, `username` may be absent because
-            # there is no request context.
-            # Prefer the associated user object when present, falling
-            # back to the legacy username attribute.
-            username = getattr(event.get('user'), 'username', None) or event.get('username')
-
             # Compile the task parameters
             params = {
                 'event_rule': event_rule,
@@ -229,7 +216,6 @@ def process_event_rules(event_rules, object_type, event):
                 'data': event_data,
                 'snapshots': event.get('snapshots'),
                 'timestamp': timezone.now().isoformat(),
-                'username': username,
                 'retry': get_rq_retry(),
             }
             if 'request' in event:

+ 1 - 1
netbox/extras/models/models.py

@@ -220,7 +220,7 @@ class Webhook(CustomFieldsMixin, ExportTemplatesMixin, TagsMixin, OwnerMixin, Ch
         help_text=_(
             "Jinja2 template for a custom request body. If blank, a JSON object representing the change will be "
             "included. Available context data includes: <code>event</code>, <code>model</code>, "
-            "<code>timestamp</code>, <code>username</code>, <code>request_id</code>, and <code>data</code>."
+            "<code>timestamp</code>, <code>request</code>, and <code>data</code>."
         )
     )
     secret = models.CharField(

+ 4 - 6
netbox/extras/tests/test_event_rules.py

@@ -396,8 +396,6 @@ class EventRuleTestCase(RQQueueTestMixin, APITestCase):
             self.assertEqual(body['event'], 'created')
             self.assertEqual(body['timestamp'], job.kwargs['timestamp'])
             self.assertEqual(body['object_type'], 'dcim.site')
-            self.assertEqual(body['username'], 'testuser')
-            self.assertEqual(body['request_id'], str(request_id))
             self.assertEqual(body['data']['name'], 'Site 1')
             self.assertEqual(body['data']['foo'], 1)
             self.assertEqual(body['context']['foo'], 123)  # From netbox.tests.dummy_plugin
@@ -431,13 +429,13 @@ class EventRuleTestCase(RQQueueTestMixin, APITestCase):
         with patch.object(Session, 'send', dummy_send):
             send_webhook(**job.kwargs)
 
-    def test_job_completed_webhook_username_fallback(self):
+    def test_job_completed_webhook_without_request(self):
         """
         Ensure job_end event processing can enqueue a webhook even when the EventContext
-        lacks legacy request attributes (e.g. `username`).
+        lacks a request context.
 
         The job_start/job_end signal receivers only populate `user` and `data`, so webhook
-        processing must derive the username from the user object (or tolerate it being unset).
+        processing must tolerate the absence of a request.
         """
         script_type = ObjectType.objects.get_for_model(Script)
         webhook_type = ObjectType.objects.get_for_model(Webhook)
@@ -459,7 +457,7 @@ class EventRuleTestCase(RQQueueTestMixin, APITestCase):
         self.assertEqual(job.kwargs['event_rule'], event_rule)
         self.assertEqual(job.kwargs['event_type'], JOB_COMPLETED)
         self.assertEqual(job.kwargs['object_type'], script_type)
-        self.assertEqual(job.kwargs['username'], self.user.username)
+        self.assertNotIn('request', job.kwargs)
 
     def test_duplicate_enqueue_refreshes_lazy_payload(self):
         """

+ 1 - 3
netbox/extras/webhooks.py

@@ -43,7 +43,7 @@ def generate_signature(request_body, secret):
 
 
 @job('default')
-def send_webhook(event_rule, object_type, event_type, data, timestamp, username, request=None, snapshots=None):
+def send_webhook(event_rule, object_type, event_type, data, timestamp, request=None, snapshots=None):
     """
     Make a POST request to the defined Webhook
     """
@@ -54,8 +54,6 @@ def send_webhook(event_rule, object_type, event_type, data, timestamp, username,
         'event': WEBHOOK_EVENT_TYPES.get(event_type, event_type),
         'timestamp': timestamp,
         'object_type': '.'.join(object_type.natural_key()),
-        'username': username,
-        'request_id': request.id if request else None,
         'data': data,
     }
     if request: