from typing import Any, Callable, Dict, List, Optional, Type, Union
from fastapi import routing
-from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
+from fastapi.openapi.docs import (
+ get_redoc_html,
+ get_swagger_ui_html,
+ get_swagger_ui_oauth2_redirect_html,
+)
from fastapi.openapi.utils import get_openapi
from fastapi.params import Depends
from pydantic import BaseModel
openapi_prefix: str = "",
docs_url: Optional[str] = "/docs",
redoc_url: Optional[str] = "/redoc",
+ swagger_ui_oauth2_redirect_url: Optional[str] = "/docs/oauth2-redirect",
**extra: Dict[str, Any],
) -> None:
self._debug = debug
self.openapi_prefix = openapi_prefix.rstrip("/")
self.docs_url = docs_url
self.redoc_url = redoc_url
+ self.swagger_ui_oauth2_redirect_url = swagger_ui_oauth2_redirect_url
self.extra = extra
self.openapi_version = "3.0.2"
async def swagger_ui_html(req: Request) -> HTMLResponse:
return get_swagger_ui_html(
- openapi_url=openapi_url, title=self.title + " - Swagger UI"
+ openapi_url=openapi_url,
+ title=self.title + " - Swagger UI",
+ oauth2_redirect_url=self.swagger_ui_oauth2_redirect_url,
)
self.add_route(self.docs_url, swagger_ui_html, include_in_schema=False)
+
+ if self.swagger_ui_oauth2_redirect_url:
+
+ async def swagger_ui_redirect(req: Request) -> HTMLResponse:
+ return get_swagger_ui_oauth2_redirect_html()
+
+ self.add_route(
+ self.swagger_ui_oauth2_redirect_url,
+ swagger_ui_redirect,
+ include_in_schema=False,
+ )
if self.openapi_url and self.redoc_url:
async def redoc_html(req: Request) -> HTMLResponse:
+from typing import Optional
+
from starlette.responses import HTMLResponse
swagger_js_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js",
swagger_css_url: str = "https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css",
swagger_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
+ oauth2_redirect_url: Optional[str] = None,
) -> HTMLResponse:
+
html = f"""
<! doctype html>
<html>
<script>
const ui = SwaggerUIBundle({{
url: '{openapi_url}',
+ """
+
+ if oauth2_redirect_url:
+ html += f"oauth2RedirectUrl: window.location.origin + '{oauth2_redirect_url}',"
+
+ html += """
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout"
-
- }})
+ })
</script>
</body>
</html>
redoc_js_url: str = "https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js",
redoc_favicon_url: str = "https://fastapi.tiangolo.com/img/favicon.png",
) -> HTMLResponse:
-
html = f"""
<!DOCTYPE html>
<html>
</html>
"""
return HTMLResponse(html)
+
+
+def get_swagger_ui_oauth2_redirect_html() -> HTMLResponse:
+ html = """
+ <!doctype html>
+ <html lang="en-US">
+ <body onload="run()">
+ </body>
+ </html>
+ <script>
+ 'use strict';
+ function run () {
+ var oauth2 = window.opener.swaggerUIRedirectOauth2;
+ var sentState = oauth2.state;
+ var redirectUrl = oauth2.redirectUrl;
+ var isValid, qp, arr;
+
+ if (/code|token|error/.test(window.location.hash)) {
+ qp = window.location.hash.substring(1);
+ } else {
+ qp = location.search.substring(1);
+ }
+
+ arr = qp.split("&")
+ arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
+ qp = qp ? JSON.parse('{' + arr.join() + '}',
+ function (key, value) {
+ return key === "" ? value : decodeURIComponent(value)
+ }
+ ) : {}
+
+ isValid = qp.state === sentState
+
+ if ((
+ oauth2.auth.schema.get("flow") === "accessCode"||
+ oauth2.auth.schema.get("flow") === "authorizationCode"
+ ) && !oauth2.auth.code) {
+ if (!isValid) {
+ oauth2.errCb({
+ authId: oauth2.auth.name,
+ source: "auth",
+ level: "warning",
+ message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
+ });
+ }
+
+ if (qp.code) {
+ delete oauth2.state;
+ oauth2.auth.code = qp.code;
+ oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
+ } else {
+ let oauthErrorMsg
+ if (qp.error) {
+ oauthErrorMsg = "["+qp.error+"]: " +
+ (qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
+ (qp.error_uri ? "More info: "+qp.error_uri : "");
+ }
+
+ oauth2.errCb({
+ authId: oauth2.auth.name,
+ source: "auth",
+ level: "error",
+ message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
+ });
+ }
+ } else {
+ oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
+ }
+ window.close();
+ }
+ </script>
+ """
+ return HTMLResponse(content=html)
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"
assert "swagger-ui-dist" in response.text
+ assert (
+ f"oauth2RedirectUrl: window.location.origin + '/docs/oauth2-redirect'"
+ in response.text
+ )
+
+
+def test_swagger_ui_oauth2_redirect():
+ response = client.get("/docs/oauth2-redirect")
+ assert response.status_code == 200
+ assert response.headers["content-type"] == "text/html; charset=utf-8"
+ assert "window.opener.swaggerUIRedirectOauth2" in response.text
def test_redoc():
--- /dev/null
+from fastapi import FastAPI
+from starlette.testclient import TestClient
+
+swagger_ui_oauth2_redirect_url = "/docs/redirect"
+
+app = FastAPI(swagger_ui_oauth2_redirect_url=swagger_ui_oauth2_redirect_url)
+
+
+@app.get("/items/")
+async def read_items():
+ return {"id": "foo"}
+
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200
+ assert response.headers["content-type"] == "text/html; charset=utf-8"
+ assert "swagger-ui-dist" in response.text
+ print(client.base_url)
+ assert (
+ f"oauth2RedirectUrl: window.location.origin + '{swagger_ui_oauth2_redirect_url}'"
+ in response.text
+ )
+
+
+def test_swagger_ui_oauth2_redirect():
+ response = client.get(swagger_ui_oauth2_redirect_url)
+ assert response.status_code == 200
+ assert response.headers["content-type"] == "text/html; charset=utf-8"
+ assert "window.opener.swaggerUIRedirectOauth2" in response.text
+
+
+def test_response():
+ response = client.get("/items/")
+ assert response.json() == {"id": "foo"}
--- /dev/null
+from fastapi import FastAPI
+from starlette.testclient import TestClient
+
+app = FastAPI(swagger_ui_oauth2_redirect_url=None)
+
+
+@app.get("/items/")
+async def read_items():
+ return {"id": "foo"}
+
+
+client = TestClient(app)
+
+
+def test_swagger_ui():
+ response = client.get("/docs")
+ assert response.status_code == 200
+ assert response.headers["content-type"] == "text/html; charset=utf-8"
+ assert "swagger-ui-dist" in response.text
+ print(client.base_url)
+ assert "oauth2RedirectUrl" not in response.text
+
+
+def test_swagger_ui_no_oauth2_redirect():
+ response = client.get("/docs/oauth2-redirect")
+ assert response.status_code == 404
+
+
+def test_response():
+ response = client.get("/items/")
+ assert response.json() == {"id": "foo"}