Skip to content

[docs] Document Django Channels WebSocket API endpoints#691

Open
iamavichal-geek wants to merge 2 commits intoopenwisp:masterfrom
iamavichal-geek:docs/websocket-api
Open

[docs] Document Django Channels WebSocket API endpoints#691
iamavichal-geek wants to merge 2 commits intoopenwisp:masterfrom
iamavichal-geek:docs/websocket-api

Conversation

@iamavichal-geek
Copy link

Closes #682

Changes:

  • docs/user/websocket-api.rst — new page documenting the RadiusBatch
    WebSocket endpoint (modelled after openwisp-firmware-upgrader)
  • docs/index.rst — registered in toctree
  • docs/user/generating_users.rst — added WebSocket cross-reference
  • docs/user/importing_users.rst — added WebSocket cross-reference
  • docs/user/settings.rst — added label + note to
    OPENWISP_RADIUS_BATCH_ASYNC_THRESHOLD

- add docs/user/websocket-api.rst (endpoint URL, auth, message types,
  JS example, deployment setup)
- register new page in docs/index.rst toctree
- add WebSocket cross-references to generating_users.rst and
  importing_users.rst
- add missing label + note to OPENWISP_RADIUS_BATCH_ASYNC_THRESHOLD
  in settings.rst

Closes openwisp#682
@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

Warning

Rate limit exceeded

@iamavichal-geek has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 54 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8c006c61-fea8-4fc5-8524-fee21540c33f

📥 Commits

Reviewing files that changed from the base of the PR and between b887625 and 8585c2b.

📒 Files selected for processing (4)
  • docs/user/generating_users.rst
  • docs/user/importing_users.rst
  • docs/user/settings.rst
  • docs/user/websocket-api.rst
📝 Walkthrough

Walkthrough

Adds documentation for a WebSocket API that streams real-time status for batch user creation/import operations. Introduces docs/user/websocket-api.rst and updates docs/index.rst, docs/user/generating_users.rst, docs/user/importing_users.rst, and docs/user/settings.rst to reference WebSocket-driven batch status delivery when batch processing runs asynchronously (via Celery) after crossing the configured OPENWISP_RADIUS_BATCH_ASYNC_THRESHOLD.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Channels as "Django Channels (ASGI)"
    participant Session as "Django Session/Auth"
    participant Celery as "Celery Worker"
    participant DB as "Database"

    Client->>Channels: CONNECT wss://host/ws/radius/batch/<batch-id>/
    Channels->>Session: Validate session & staff permissions
    Session-->>Channels: Auth result
    alt Authenticated
        Channels-->>Client: Accept connection
        Note over Channels,Celery: Batch created/queued via REST import or generation
        Celery->>DB: Process batch asynchronously
        Celery-->>Channels: Publish batch_status_update (pending/processing/completed/failed)
        Channels-->>Client: Push `batch_status_update` (only terminal statuses guaranteed)
        Client->>Channels: Close (optional)
        Channels-->>Client: Close connection
    else Unauthorized
        Channels-->>Client: Reject connection
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: documenting Django Channels WebSocket API endpoints.
Description check ✅ Passed The PR description adequately lists all changes made and references the closed issue, though the checklist section is incomplete.
Linked Issues check ✅ Passed All coding requirements from issue #682 are met: WebSocket endpoint documentation, message types, authentication/authorization, and developer guidance are provided.
Out of Scope Changes check ✅ Passed All changes are within scope and directly support documenting WebSocket endpoints as required by issue #682.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kilo-code-bot
Copy link

kilo-code-bot bot commented Mar 7, 2026

Code Review Summary

Status: 1 Critical Issue Found | Recommendation: Fix before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
openwisp_radius/consumers.py 19 Authorization Bypass: _user_can_access_batch() always returns True for non-superusers, allowing any staff user to access any batch regardless of organization ownership. The .exists() result on line 18 is discarded.
Other Observations (not in diff)

The _user_can_access_batch() method in consumers.py has a bug at lines 16-19:

Current code:

RadiusBatch.objects.filter(
    pk=batch_id, organization__in=user.organizations_managed
).exists()  # Result discarded!
return True  # Always returns True

Should be:

return RadiusBatch.objects.filter(
    pk=batch_id, organization__in=user.organizations_managed
).exists()

This is a security vulnerability - the documentation at line 47-57 correctly describes org-scoped access control, but the implementation doesn't enforce it.

Files Reviewed (5 files)
  • docs/index.rst - Added WebSocket API to table of contents
  • docs/user/websocket-api.rst - New WebSocket API documentation
  • docs/user/generating_users.rst - Added WebSocket reference
  • docs/user/importing_users.rst - Added WebSocket reference
  • docs/user/settings.rst - Added cross-reference

Fix these issues in Kilo Cloud

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/user/websocket-api.rst`:
- Around line 91-139: The docs incorrectly guarantee "exactly one message per
connection"; update the text to state that RadiusBatchConsumer simply forwards
the single group_send emitted by RadiusBatch.process() and does not send the
current batch state on connect, so a client that subscribes after the terminal
group_send has already been published may receive zero messages; change the
wording to clarify that the server will send at most one terminal
batch_status_update (completed/failed) for a given batch and recommend clients
handle the zero-message case (e.g., by polling the REST API or re-subscribing)
instead of relying on a guaranteed message on connect.
- Around line 175-180: Update the wording to clarify that adding "daphne" as the
first entry in INSTALLED_APPS is only to override Django's runserver for local
development (to enable ASGI/WebSocket support in manage.py runserver), and does
not affect production ASGI deployment; mention that in production you run the
ASGI server directly (e.g., daphne myproject.asgi:application) and that
INSTALLED_APPS ordering is not used to choose the production server.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6a59e4d6-fd0f-4cb7-a186-3156f0e4dc82

📥 Commits

Reviewing files that changed from the base of the PR and between 44117af and 99e0176.

📒 Files selected for processing (5)
  • docs/index.rst
  • docs/user/generating_users.rst
  • docs/user/importing_users.rst
  • docs/user/settings.rst
  • docs/user/websocket-api.rst
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Python==3.13 | django~=5.1.0
  • GitHub Check: Python==3.12 | django~=5.2.0
  • GitHub Check: Python==3.12 | django~=4.2.0
  • GitHub Check: Python==3.13 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=4.2.0
  • GitHub Check: Python==3.12 | django~=5.1.0
  • GitHub Check: Python==3.10 | django~=5.2.0
  • GitHub Check: Python==3.10 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=5.2.0
  • GitHub Check: Python==3.11 | django~=5.1.0
  • GitHub Check: Python==3.11 | django~=4.2.0

Comment on lines +91 to +139
After the connection is established, the client does not need to send
any messages. The server pushes exactly **one** message when batch
processing finishes (either successfully or with an error).

Message type: ``batch_status_update``

.. code-block:: javascript

{
"status": "<status>"
}

The ``status`` field contains one of the following values:

.. list-table::
:header-rows: 1

* - Value
- Description
* - ``"pending"``
- The batch has been created but processing has not yet started.
This value is not sent via WebSocket; it is visible only through
the REST API or admin interface.
* - ``"processing"``
- The batch is currently being processed. This value is not sent
via WebSocket; it is the status visible when the admin page is
opened and the WebSocket connection is established.
* - ``"completed"``
- Batch processing finished successfully. This is a terminal status.
* - ``"failed"``
- Batch processing encountered an error. This is a terminal status.

.. note::

The server sends exactly one message per connection, always with a
terminal status (``"completed"`` or ``"failed"``). The client should
close the connection after receiving it.

Connection Lifecycle
++++++++++++++++++++

1. The client connects to the endpoint with the batch UUID in the URL.
2. If the user is authorized, the connection is accepted and the client
is added to the channel group ``radius_batch_<batch-id>``.
3. When batch processing finishes, the server sends one
``batch_status_update`` message containing the terminal status.
4. The client should close the connection upon receiving ``"completed"``
or ``"failed"``.
5. On disconnect, the client is removed from the channel group.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't promise a terminal message for every accepted connection.

RadiusBatchConsumer only forwards the single group_send emitted from RadiusBatch.process(); it does not send the current batch state on connect. A client that subscribes after that terminal event has already been published will receive zero messages, so the current “exactly one message per connection” wording can leave integrators waiting forever.

Suggested doc fix
-After the connection is established, the client does not need to send
-any messages. The server pushes exactly **one** message when batch
-processing finishes (either successfully or with an error).
+After the connection is established, the client does not need to send
+any messages. The server pushes at most **one** terminal message
+(``"completed"`` or ``"failed"``) to clients that are already connected
+when the terminal update is emitted.
@@
-    The server sends exactly one message per connection, always with a
-    terminal status (``"completed"`` or ``"failed"``). The client should
-    close the connection after receiving it.
+    The server does not replay the last known status on connect. Clients
+    that subscribe after processing has already finished will not receive
+    a WebSocket message and should fall back to the REST API or a
+    client-side timeout.
@@
-3. When batch processing finishes, the server sends one
-   ``batch_status_update`` message containing the terminal status.
+3. When batch processing finishes, the server sends one
+   ``batch_status_update`` message to clients that are already subscribed.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
After the connection is established, the client does not need to send
any messages. The server pushes exactly **one** message when batch
processing finishes (either successfully or with an error).
Message type: ``batch_status_update``
.. code-block:: javascript
{
"status": "<status>"
}
The ``status`` field contains one of the following values:
.. list-table::
:header-rows: 1
* - Value
- Description
* - ``"pending"``
- The batch has been created but processing has not yet started.
This value is not sent via WebSocket; it is visible only through
the REST API or admin interface.
* - ``"processing"``
- The batch is currently being processed. This value is not sent
via WebSocket; it is the status visible when the admin page is
opened and the WebSocket connection is established.
* - ``"completed"``
- Batch processing finished successfully. This is a terminal status.
* - ``"failed"``
- Batch processing encountered an error. This is a terminal status.
.. note::
The server sends exactly one message per connection, always with a
terminal status (``"completed"`` or ``"failed"``). The client should
close the connection after receiving it.
Connection Lifecycle
++++++++++++++++++++
1. The client connects to the endpoint with the batch UUID in the URL.
2. If the user is authorized, the connection is accepted and the client
is added to the channel group ``radius_batch_<batch-id>``.
3. When batch processing finishes, the server sends one
``batch_status_update`` message containing the terminal status.
4. The client should close the connection upon receiving ``"completed"``
or ``"failed"``.
5. On disconnect, the client is removed from the channel group.
After the connection is established, the client does not need to send
any messages. The server pushes at most **one** terminal message
(``"completed"`` or ``"failed"``) to clients that are already connected
when the terminal update is emitted.
Message type: ``batch_status_update``
.. code-block:: javascript
{
"status": "<status>"
}
The ``status`` field contains one of the following values:
.. list-table::
:header-rows: 1
* - Value
- Description
* - ``"pending"``
- The batch has been created but processing has not yet started.
This value is not sent via WebSocket; it is visible only through
the REST API or admin interface.
* - ``"processing"``
- The batch is currently being processed. This value is not sent
via WebSocket; it is the status visible when the admin page is
opened and the WebSocket connection is established.
* - ``"completed"``
- Batch processing finished successfully. This is a terminal status.
* - ``"failed"``
- Batch processing encountered an error. This is a terminal status.
.. note::
The server does not replay the last known status on connect. Clients
that subscribe after processing has already finished will not receive
a WebSocket message and should fall back to the REST API or a
client-side timeout.
Connection Lifecycle
+++++++++++++++++++
1. The client connects to the endpoint with the batch UUID in the URL.
2. If the user is authorized, the connection is accepted and the client
is added to the channel group ``radius_batch_<batch-id>``.
3. When batch processing finishes, the server sends one
``batch_status_update`` message to clients that are already subscribed.
4. The client should close the connection upon receiving ``"completed"``
or ``"failed"``.
5. On disconnect, the client is removed from the channel group.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 91 - 139, The docs incorrectly
guarantee "exactly one message per connection"; update the text to state that
RadiusBatchConsumer simply forwards the single group_send emitted by
RadiusBatch.process() and does not send the current batch state on connect, so a
client that subscribes after the terminal group_send has already been published
may receive zero messages; change the wording to clarify that the server will
send at most one terminal batch_status_update (completed/failed) for a given
batch and recommend clients handle the zero-message case (e.g., by polling the
REST API or re-subscribing) instead of relying on a guaranteed message on
connect.

Comment on lines +175 to +180
Django's default WSGI server does not support WebSockets. You must use
an ASGI-compatible server such as `Daphne
<https://github.com/django/daphne>`_.

Install Daphne and add it as the **first entry** in ``INSTALLED_APPS``
so that Django uses it as the ASGI server:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

According to the official Django Channels and Daphne documentation, when and why should daphnebe added as the first entry inINSTALLED_APPS? Is that required for production ASGI deployment, or mainly for Daphne's runserver integration?

💡 Result:

Official docs say to put "daphne" at the start of INSTALLED_APPS when you want Daphne to provide/override Django’s runserver command (i.e., use Daphne as the development server for ASGI), and to ensure it’s ordered before apps like django.contrib.staticfiles that also hook runserver. In Channels 4.x this is explicitly called out because runserver moved into the daphne package and Channels adds a system check to enforce the ordering. [1], [2], [3]

This is not a general requirement for production ASGI deployment. For production you run an ASGI server directly (e.g., daphne myproject.asgi:application), and that does not depend on daphne being first in INSTALLED_APPS; the “put it first” guidance is mainly for Daphne’s runserver integration. [1], [2]

Sources:
[1] Django docs: “How to use Django with Daphne” (runserver integration and “add daphne to the start of INSTALLED_APPS”) (docs.djangoproject.com)
[2] Channels docs: “4.0.0 Release Notes” (runserver moved to daphne; ordering before django.contrib.staticfiles; system check) (channels.readthedocs.io)
[3] Channels tutorial (Daphne in installed apps replaces the standard dev server for runserver) (channels.readthedocs.io)

Citations:


Clarify that daphne in INSTALLED_APPS is for development runserver integration, not production ASGI deployment.

The phrase "so that Django uses it as the ASGI server" is misleading. Per official Django/Channels docs, putting daphne first in INSTALLED_APPS is specifically to override Django's runserver command for development. Production ASGI deployment does not depend on INSTALLED_APPS ordering—you run the server directly (e.g., daphne myproject.asgi:application). The current wording conflates development and production concerns and should clarify the actual purpose.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 175 - 180, Update the wording to
clarify that adding "daphne" as the first entry in INSTALLED_APPS is only to
override Django's runserver for local development (to enable ASGI/WebSocket
support in manage.py runserver), and does not affect production ASGI deployment;
mention that in production you run the ASGI server directly (e.g., daphne
myproject.asgi:application) and that INSTALLED_APPS ordering is not used to
choose the production server.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
docs/user/websocket-api.rst (2)

91-93: ⚠️ Potential issue | 🟠 Major

Don't guarantee a terminal message on every connection.

RadiusBatchConsumer does not replay the current batch status on connect; it only forwards the single group_send emitted by RadiusBatch.process(). A client that subscribes after that terminal event has already been published will receive zero messages, so the repeated “exactly one message per connection” wording is inaccurate and can leave integrators waiting indefinitely.

Suggested doc fix
-After the connection is established, the client does not need to send any
-messages. The server pushes exactly **one** message when batch processing
-finishes (either successfully or with an error).
+After the connection is established, the client does not need to send any
+messages. The server pushes at most **one** terminal message
+(``"completed"`` or ``"failed"``) to clients that are already connected
+when the terminal update is emitted.
@@
-    The server sends exactly one message per connection, always with a
-    terminal status (``"completed"`` or ``"failed"``). The client should
-    close the connection after receiving it.
+    The server does not replay the last known status on connect. Clients
+    that subscribe after processing has already finished may receive no
+    WebSocket message and should fall back to the REST API or a
+    client-side timeout.
@@
-3. When batch processing finishes, the server sends one
-   ``batch_status_update`` message containing the terminal status.
+3. When batch processing finishes, the server sends one
+   ``batch_status_update`` message to clients that are already subscribed.

Also applies to: 125-127, 135-136

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 91 - 93, Update the wording to
remove the guarantee of a terminal message per connection: clarify that
RadiusBatchConsumer forwards the single group_send emitted by
RadiusBatch.process() and therefore the server may push at most one message (and
possibly zero if the terminal event occurred before the client subscribed);
mention RadiusBatchConsumer and RadiusBatch.process() so integrators know the
consumer does not replay state on connect and may miss the event.

179-180: ⚠️ Potential issue | 🟡 Minor

Clarify that daphne first in INSTALLED_APPS is for runserver, not production server selection.

This wording implies INSTALLED_APPS decides the production ASGI server. In practice, putting daphne first is about Daphne's runserver integration for development; production runs the ASGI server directly.

According to the official Django and Channels documentation, is placing "daphne" first in INSTALLED_APPS required for production ASGI deployment, or is it specifically for Daphne's manage.py runserver integration?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 179 - 180, Update the sentence
about installing "daphne" so it clearly states that placing "daphne" first in
INSTALLED_APPS is required only to enable Daphne's manage.py runserver
integration during development, and does not determine which ASGI server is used
in production (production should run the ASGI server process directly);
reference the symbols "daphne", "INSTALLED_APPS", "runserver" and "ASGI server"
in the clarification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/user/websocket-api.rst`:
- Around line 91-93: Update the wording to remove the guarantee of a terminal
message per connection: clarify that RadiusBatchConsumer forwards the single
group_send emitted by RadiusBatch.process() and therefore the server may push at
most one message (and possibly zero if the terminal event occurred before the
client subscribed); mention RadiusBatchConsumer and RadiusBatch.process() so
integrators know the consumer does not replay state on connect and may miss the
event.
- Around line 179-180: Update the sentence about installing "daphne" so it
clearly states that placing "daphne" first in INSTALLED_APPS is required only to
enable Daphne's manage.py runserver integration during development, and does not
determine which ASGI server is used in production (production should run the
ASGI server process directly); reference the symbols "daphne", "INSTALLED_APPS",
"runserver" and "ASGI server" in the clarification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c87b638-5486-434b-8548-bd4789bdb51b

📥 Commits

Reviewing files that changed from the base of the PR and between 99e0176 and b4fbb6a.

📒 Files selected for processing (4)
  • docs/user/generating_users.rst
  • docs/user/importing_users.rst
  • docs/user/settings.rst
  • docs/user/websocket-api.rst

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
docs/user/websocket-api.rst (2)

91-93: ⚠️ Potential issue | 🟠 Major

Don't promise a terminal message for every accepted connection.

The consumer forwards the single group_send emitted when processing finishes, but it does not replay the current batch state on connect. A client that subscribes after that terminal event has already been published will receive zero messages, so the repeated “exactly one message per connection” wording is inaccurate.

Suggested doc fix
-After the connection is established, the client does not need to send any
-messages. The server pushes exactly **one** message when batch processing
-finishes (either successfully or with an error).
+After the connection is established, the client does not need to send any
+messages. The server may push **one** terminal message when batch
+processing finishes, but only to clients that are already connected when
+the update is emitted.
@@
-    The server sends exactly one message per connection, always with a
-    terminal status (``"completed"`` or ``"failed"``). The client should
-    close the connection after receiving it.
+    The server does not replay the last known status on connect. Clients
+    that subscribe after processing has already finished may receive no
+    WebSocket message and should fall back to the REST API or a client-
+    side timeout.
@@
-3. When batch processing finishes, the server sends one
-   ``batch_status_update`` message containing the terminal status.
+3. When batch processing finishes, the server sends one
+   ``batch_status_update`` message to clients that are already subscribed.

Also applies to: 123-127, 132-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 91 - 93, The docs currently claim
the server pushes "exactly one" message per connection; update the text to
clarify that the consumer emits a single group_send when processing finishes but
it does not replay current batch state on new connections, so a client that
connects after that terminal group_send may receive zero messages; revise all
occurrences (around the paragraphs referencing consumer and group_send) to state
"at most one message if the client was connected when the terminal group_send
was emitted" and mention that late subscribers will not receive past terminal
events.

179-180: ⚠️ Potential issue | 🟡 Minor

Clarify that daphne app ordering is for development runserver, not production ASGI selection.

Putting "daphne" first in INSTALLED_APPS affects Daphne's Django runserver integration; production ASGI deployment depends on the server process you run, not app ordering. The current wording conflates those two concerns.

According to the official Django and Channels documentation, why should "daphne" be added first in INSTALLED_APPS, and does that affect production ASGI deployment or only Daphne's runserver integration?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 179 - 180, The documentation
currently implies adding "daphne" first in INSTALLED_APPS selects Daphne as the
ASGI server; update the text to clarify that placing "daphne" first in
INSTALLED_APPS is only necessary for Django's development runserver integration
(so Daphne's runserver command/overrides take precedence), and does not
determine which ASGI server is used in production — production ASGI deployment
is chosen by the server process you run (e.g., daphne, uvicorn) and not by
INSTALLED_APPS ordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/user/websocket-api.rst`:
- Around line 47-57: The docs state org-scoped access control for websocket
batch access, but openwisp_radius/consumers.py's _user_can_access_batch()
currently discards the QuerySet.exists() result and ends up returning True for
non-superusers, so the code does not actually enforce batch ownership; fix
_user_can_access_batch() to properly evaluate and return the boolean from the
ownership check (ensure the .exists() or equivalent filter result is returned
and used in the authorization decision) or, if you prefer a docs change instead,
soften the wording in docs to remove the assertion that organization manager
ownership is enforced until _user_can_access_batch() correctly checks and
returns the ownership boolean.

---

Duplicate comments:
In `@docs/user/websocket-api.rst`:
- Around line 91-93: The docs currently claim the server pushes "exactly one"
message per connection; update the text to clarify that the consumer emits a
single group_send when processing finishes but it does not replay current batch
state on new connections, so a client that connects after that terminal
group_send may receive zero messages; revise all occurrences (around the
paragraphs referencing consumer and group_send) to state "at most one message if
the client was connected when the terminal group_send was emitted" and mention
that late subscribers will not receive past terminal events.
- Around line 179-180: The documentation currently implies adding "daphne" first
in INSTALLED_APPS selects Daphne as the ASGI server; update the text to clarify
that placing "daphne" first in INSTALLED_APPS is only necessary for Django's
development runserver integration (so Daphne's runserver command/overrides take
precedence), and does not determine which ASGI server is used in production —
production ASGI deployment is chosen by the server process you run (e.g.,
daphne, uvicorn) and not by INSTALLED_APPS ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5c44fee1-8aaa-468f-b63b-f9be2822d829

📥 Commits

Reviewing files that changed from the base of the PR and between b4fbb6a and 9b9c193.

📒 Files selected for processing (4)
  • docs/user/generating_users.rst
  • docs/user/importing_users.rst
  • docs/user/settings.rst
  • docs/user/websocket-api.rst

Comment on lines +47 to +57
A user is authorized if:

- The user is a superuser, OR
- The user:

- Is authenticated and marked as staff, AND
- Is an organization manager for the organization that owns the
requested batch.

If any check fails, the server closes the connection without sending any
message.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don't document org-scoped access control unless the consumer actually enforces it.

openwisp_radius/consumers.py currently discards the .exists() result in _user_can_access_batch() and then returns True for non-superusers, so this section overstates the authorization guarantee. Please either fix the consumer before merging these docs or soften the wording until batch ownership is really checked.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 47 - 57, The docs state org-scoped
access control for websocket batch access, but openwisp_radius/consumers.py's
_user_can_access_batch() currently discards the QuerySet.exists() result and
ends up returning True for non-superusers, so the code does not actually enforce
batch ownership; fix _user_can_access_batch() to properly evaluate and return
the boolean from the ownership check (ensure the .exists() or equivalent filter
result is returned and used in the authorization decision) or, if you prefer a
docs change instead, soften the wording in docs to remove the assertion that
organization manager ownership is enforced until _user_can_access_batch()
correctly checks and returns the ownership boolean.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
docs/user/websocket-api.rst (3)

91-137: ⚠️ Potential issue | 🟠 Major

Avoid guaranteeing one terminal message per connection.

The consumer only forwards the single group_send emitted when processing finishes; it does not replay the last-known batch state on connect. A client that subscribes after that terminal event has already been published may receive zero messages, so this section should say “at most one terminal update” and recommend a REST fallback or timeout for late subscribers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 91 - 137, Update the documentation
to avoid promising exactly one terminal message per WebSocket connection: change
wording in the note and the Connection Lifecycle steps (references: message type
"batch_status_update" and channel group "radius_batch_<batch-id>") to state that
the server will send at most one terminal update (i.e., clients may receive zero
or one terminal message) and recommend a REST fallback or client-side
timeout/retry for late subscribers; also adjust the sentence in step 4 (closing
on "completed") to reflect the conditional nature (close if/when a terminal
message is received).

47-57: ⚠️ Potential issue | 🔴 Critical

Don't document org-scoped batch access until the consumer enforces it.

openwisp_radius/consumers.py:1-47 still returns True for non-superusers after calling .exists() without using the result, so the current implementation does not reliably enforce “organization manager for the organization that owns the requested batch”. Either fix RadiusBatchConsumer._user_can_access_batch() first or soften this section to avoid promising an authorization check that is not actually applied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 47 - 57, The docs claim org-scoped
batch access is enforced but RadiusBatchConsumer._user_can_access_batch in
openwisp_radius/consumers.py currently calls .exists() and then still returns
True for non-superusers; fix _user_can_access_batch so it returns the actual
boolean result of the queryset.exists() check (or uses the queryset truthiness)
instead of ignoring it, ensuring non-superusers only get True when they are
authenticated staff and an organization manager for the batch owner; update the
implementation in RadiusBatchConsumer._user_can_access_batch to short-circuit
for superusers and otherwise return the exists() value (or equivalent) so the
authorization described in websocket-api.rst is actually enforced.

179-180: ⚠️ Potential issue | 🟡 Minor

Clarify that daphne first in INSTALLED_APPS is for runserver, not production server selection.

This wording is misleading: putting daphne first in INSTALLED_APPS is for Daphne’s Django runserver integration, not what makes production “use it as the ASGI server”. Production ASGI deployment runs the server directly, so the docs should separate local development setup from production deployment.

According to the official Django and Channels documentation, when and why should "daphne" be added as the first entry in INSTALLED_APPS? Is that required for production ASGI deployment, or mainly for Daphne's runserver integration?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user/websocket-api.rst` around lines 179 - 180, Update the sentence to
clarify that placing "daphne" first in INSTALLED_APPS is only required to enable
Daphne’s runserver integration for local development (so Django's manage.py
runserver uses Daphne), and is NOT how you select the ASGI server in production
— in production you start Daphne (or Uvicorn/Hypercorn) directly as the ASGI
server. Replace the current line with two brief lines separating development
(add "daphne" first in INSTALLED_APPS for runserver integration) and production
(run the chosen ASGI server directly), and mention "daphne", "INSTALLED_APPS",
and "runserver" so readers can find the exact settings to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@docs/user/websocket-api.rst`:
- Around line 91-137: Update the documentation to avoid promising exactly one
terminal message per WebSocket connection: change wording in the note and the
Connection Lifecycle steps (references: message type "batch_status_update" and
channel group "radius_batch_<batch-id>") to state that the server will send at
most one terminal update (i.e., clients may receive zero or one terminal
message) and recommend a REST fallback or client-side timeout/retry for late
subscribers; also adjust the sentence in step 4 (closing on "completed") to
reflect the conditional nature (close if/when a terminal message is received).
- Around line 47-57: The docs claim org-scoped batch access is enforced but
RadiusBatchConsumer._user_can_access_batch in openwisp_radius/consumers.py
currently calls .exists() and then still returns True for non-superusers; fix
_user_can_access_batch so it returns the actual boolean result of the
queryset.exists() check (or uses the queryset truthiness) instead of ignoring
it, ensuring non-superusers only get True when they are authenticated staff and
an organization manager for the batch owner; update the implementation in
RadiusBatchConsumer._user_can_access_batch to short-circuit for superusers and
otherwise return the exists() value (or equivalent) so the authorization
described in websocket-api.rst is actually enforced.
- Around line 179-180: Update the sentence to clarify that placing "daphne"
first in INSTALLED_APPS is only required to enable Daphne’s runserver
integration for local development (so Django's manage.py runserver uses Daphne),
and is NOT how you select the ASGI server in production — in production you
start Daphne (or Uvicorn/Hypercorn) directly as the ASGI server. Replace the
current line with two brief lines separating development (add "daphne" first in
INSTALLED_APPS for runserver integration) and production (run the chosen ASGI
server directly), and mention "daphne", "INSTALLED_APPS", and "runserver" so
readers can find the exact settings to change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a298c1da-9f66-48a3-890f-3674d7cd0a53

📥 Commits

Reviewing files that changed from the base of the PR and between 9b9c193 and b887625.

📒 Files selected for processing (4)
  • docs/user/generating_users.rst
  • docs/user/importing_users.rst
  • docs/user/settings.rst
  • docs/user/websocket-api.rst

- add docs/user/websocket-api.rst (endpoint URL, auth, message types,
  JS example, deployment setup)
- register new page in docs/index.rst toctree
- add WebSocket cross-references to generating_users.rst and
  importing_users.rst
- add missing label + note to OPENWISP_RADIUS_BATCH_ASYNC_THRESHOLD
  in settings.rst

Closes openwisp#682
@coveralls
Copy link

Coverage Status

coverage: 97.426%. remained the same
when pulling 8585c2b on iamavichal-geek:docs/websocket-api
into eb34b91 on openwisp:master.

@iamavichal-geek
Copy link
Author

Hi @nemesifier, I've opened this PR to address #682. Modelled the structure after openwisp-firmware-upgrader's websocket-api.rst as suggested in the issue. Let me know if anything needs adjusting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[docs] Document Django Channels WebSocket endpoints

2 participants