8000 Allow disabling docs UIs by disabling OpenAPI by tiangolo · Pull Request #1421 · fastapi/fastapi · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
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

Allow disabling docs UIs by disabling OpenAPI #1421

Merged
merged 3 commits into from
May 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions docs/en/docs/advanced/conditional-openapi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Conditional OpenAPI

If you needed to, you could use settings and environment variables to configure OpenAPI conditionally depending on the environment, and even disable it entirely.

## About security, APIs, and docs

Hiding your documentation user interfaces in production *shouldn't* be the way to protect your API.

That doesn't add any extra security to your API, the *path operations* will still be available where they are.

If there's a security flaw in your code, it will still exist.

Hiding the documentation just makes it more difficult to understand how to interact with your API, and could make it more difficult for you to debug it in production. It could be considered simply a form of <a href="https://en.wikipedia.org/wiki/Security_through_obscurity" class="external-link" target="_blank">Security through obscurity</a>.

If you want to secure your API, there are several better things you can do, for example:

* Make sure you have well defined Pydantic models for your request bodies and responses.
* Configure any required permissions and roles using dependencies.
* Never store plaintext passwords, only password hashes.
* Implement and use well-known cryptographic tools, like Passlib and JWT tokens, etc.
* Add more granular permission controls with OAuth2 scopes where needed.
* ...etc.

Nevertheless, you might have a very specific use case where you really need to disable the API docs for some environment (e.g. for production) or depending on configurations from environment variables.

## Conditional OpenAPI from settings and env vars

You can easily use the same Pydantic settings to configure your generated OpenAPI and the docs UIs.

For example:

```Python hl_lines="6 11"
{!../../../docs_src/conditional_openapi/tutorial001.py!}
```

Here we declare the setting `openapi_url` with the same default of `"/openapi.json"`.

And then we use it when creating the `FastAPI` app.

Then you could disable OpenAPI (including the UI docs) by setting the environment variable `OPENAPI_URL` to the empty string, like:

<div class="termy">

```console
$ OPENAPI_URL= uvicorn main:app

<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
```

</div>

Then if you go to the URLs at `/openapi.json`, `/docs`, or `/redoc` you will just get a `404 Not Found` error like:

```JSON
{
"detail": "Not Found"
}
```
2 changes: 1 addition & 1 deletion docs/en/docs/tutorial/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ For example, to set it to be served at `/api/v1/openapi.json`:
{!../../../docs_src/metadata/tutorial002.py!}
```

If you want to disable the OpenAPI schema completely you can set `openapi_url=None`.
If you want to disable the OpenAPI schema completely you can set `openapi_url=None`, that will also disable the documentation user interfaces that use it.

## Docs URLs

Expand Down
1 change: 1 addition & 0 deletions docs/en/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ nav:
- advanced/testing-dependencies.md
- advanced/testing-database.md
- advanced/settings.md
- advanced/conditional-openapi.md
- advanced/extending-openapi.md
- advanced/openapi-callbacks.md
- advanced/wsgi.md
Expand Down
16 changes: 16 additions & 0 deletions docs_src/conditional_openapi/tutorial001.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from fastapi import FastAPI
from pydantic import BaseSettings


class Settings(BaseSettings):
openapi_url: str = "/openapi.json"


settings = Settings()

app = FastAPI(openapi_url=settings.openapi_url)


@app.get("/")
def root():
return {"message": "Hello World"}
3 changes: 0 additions & 3 deletions fastapi/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,6 @@ def __init__(
if self.openapi_url:
assert self.title, "A title must be provided for OpenAPI, e.g.: 'My API'"
assert self.version, "A version must be provided for OpenAPI, e.g.: '2.1.0'"

if self.docs_url or self.redoc_url:
assert self.openapi_url, "The openapi_url is required for the docs"
self.openapi_schema: Optional[Dict[str, Any]] = None
self.setup()

Expand Down
Empty file.
46 changes: 46 additions & 0 deletions tests/test_tutorial/test_conditional_openapi/test_tutorial001.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import importlib

from fastap 963B i.testclient import TestClient

from conditional_openapi import tutorial001

openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/": {
"get": {
"summary": "Root",
"operationId": "root__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
}
}
},
}


def test_default_openapi():
client = TestClient(tutorial001.app)
response = client.get("/openapi.json")
assert response.json() == openapi_schema
response = client.get("/docs")
assert response.status_code == 200, response.text
response = client.get("/redoc")
assert response.status_code == 200, response.text


def test_disable_openapi(monkeypatch):
monkeypatch.setenv("OPENAPI_URL", "")
importlib.reload(tutorial001)
client = TestClient(tutorial001.app)
response = client.get("/openapi.json")
assert response.status_code == 404, response.text
response = client.get("/docs")
assert response.status_code == 404, response.text
response = client.get("/redoc")
assert response.status_code == 404, response.text
0