Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Websocket Denial Response #1478

Closed
wants to merge 9 commits into from

Conversation

paulo-raca
Copy link

@paulo-raca paulo-raca commented Feb 8, 2022

When a websocket endpoint throws an exception (e.g., because of invalid authentication), we currently end with either with a generic 403 response (triggered by "websocket.close" message) or a generic HTTP500 response (if there is an unhandled error)

Instead, it is better to return useful HTTP error responses using the Websocket Denial Response ASGI extension, if it is available

This PR makes several changes in order to support this extension:

  • Added WebsocketDenialResponse class to return a HTTP Response to a websocket request (if the extension is supported, otherwise just closes with code 403 as before)
  • AuthenticationMiddleware and @requires have been updated (Fixes Wrong unauthorized WebSocket status code #1362)
  • HTTPSRedirectMiddleware has been updated
    • Improved the URL mapping code
    • Added an argument to specify the port mappings (e.g., if the app is running on ports 8080/8443 instead of 80/443)
  • ExceptionMiddleware and ServerErrorMiddleware have been updated to take advantage of it
    • Both classes have been refactored to share code, and a new BaseExceptionMiddleware superclass was added
    • ExceptionTypeOrStatusCode and ExceptionHandler types were added to simplify typing annotations
    • Error handlers now take a HTTPConnection instead of a Request

@paulo-raca
Copy link
Author

Here is a simple demo (Based on FastAPI):

(It needs to be executed with hypercorn, as it is the only ASGI server currently supporting this extension)

app = FastAPI(debug=True)

@app.websocket("/ws")
async def websocket_endpoint(ws: WebSocket, err: int = None):
    if err == 500:
        raise Exception("Internal Server Error")
    elif err is not None:
        raise HTTPException(status_code=err)
    await ws.accept()
    await ws.send_text("Hello")

No error:

$python -m websockets "ws://localhost:8000/ws"
Connected to ws://localhost:8000/ws.
< Hello
Connection closed: 1000 (OK).

With custom HTTP code:

$ python -m websockets "ws://localhost:8000/ws?err=404"
Failed to connect to ws://localhost:8000/ws?err=404: server rejected WebSocket connection: HTTP 404.

Response body:
{"detail":"Not Found"}

With uncaught exception

$ python -m websockets "ws://localhost:8000/ws?err=500"
Failed to connect to ws://localhost:8000/ws?err=500: server rejected WebSocket connection: HTTP 500.

Response body:

Traceback (most recent call last):
  [...]
Exception: Internal Server Error

@paulo-raca paulo-raca changed the title Support for Websocket Denial Response in ExceptionMiddleware Support for Websocket Denial Response Feb 9, 2022
@paulo-raca paulo-raca force-pushed the exception_websocket branch 2 times, most recently from a7372ee to bd1e82e Compare February 9, 2022 05:29
It allows returning regular HTTP Response to a websocket request (if the ASGI Websocket Denial Response extension is supported, otherwise just closes with code 403 as before)
The current implementation was broken when used with websockets

This URL mapping logic has also been update:
- Fixing a few minor bugs
- It is now possible to specify the HTTP/HTTPS port mapping (e.g. 80/443, 8080/8443, etc)
- Both classes now share a common superclass, BaseExceptionMiddleware, which takes care of most of the logic
- Added ExceptionTypeOrStatusCode and ExceptionHandler to help on typing hints
- Error handlers now take a HTTPConnection instead of a Request
- Support for WebsocketDenialResponse
@adriangb adriangb added the feature New feature or request label Feb 11, 2022
@paulo-raca paulo-raca marked this pull request as draft March 3, 2022 16:45
paulo-raca added a commit to paulo-raca/starlette that referenced this pull request Mar 3, 2022
When a websocket connection is rejected (e.g., wrong URL, missing permissions, etc),
starlette always returns a generic 403 response (triggered by "websocket.close" message)
or a generic HTTP500 response (Produced by ASGI gateway if there is an unhandled error)

Instead it is better to return useful HTTP error responses using the [Websocket Denial Response ASGI extension](https://asgi.readthedocs.io/en/latest/extensions.html#websocket-denial-response), if it is available

This PR makes the following changes:
- Added WebsocketDenialResponse class to return a HTTP Response to a websocket request. mapping the ASGI message as appropriate.
  if the extension is not supported, it just closes the websocket resulting and HTTP 403 like before

- Updated testclient to support denial responses.
  The extension must be explicitly enabled on a connection using `websocket_connect(..., denial_response=True)`,
  and if a denial response is returned a WebSocketDenied exception is thrown

- Refactored testclient code to parse HTTP responses so that it can be reused on websocket denial responses

This PR superseeds encode#1478 with better documentation and testing support. Unlike the previous PR,
it does not update the various middlewares.

Instead, I'll create separate PRs for those later, in order to keep this PR smaller
@paulo-raca
Copy link
Author

paulo-raca commented Mar 3, 2022

Superseeded by #1315

@paulo-raca paulo-raca closed this Mar 3, 2022
@Kludex
Copy link
Sponsor Member

Kludex commented Apr 15, 2022

Superseded by #1535 actually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Wrong unauthorized WebSocket status code
3 participants