Skip to content
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,20 @@ Sometimes, it is necessary to specify the protocol and port to access the openap
config.pyramid_openapi3_add_explorer(proto_port=('https', 443))
```

### CSP nonce

If a Content Security Policy (CSP) is used in your Pyramid application, you can pass a nonce to the OpenAPI explorer UI by setting the `csp_nonce` request parameter:

```python
def inject_csp_header_tween(request):
nonce = secrets.token_urlsafe(16)
request.csp_nonce = nonce
response = handler(request)
response.headers["Content-Security-Policy"] = f"script-src 'self' 'nonce-{nonce}'"
return response
```


## Demo / Examples

There are three examples provided with this package:
Expand Down
5 changes: 5 additions & 0 deletions pyramid_openapi3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,15 @@ def explorer_view(request: Request) -> Response:
}
if ui_config:
merged_ui_config.update(ui_config)
# Check if request has a CSP nonce (for Content Security Policy)
nonce = getattr(request, "csp_nonce", None)
nonce_attr = f' nonce="{nonce}"' if nonce else ""

html = template.safe_substitute(
ui_version=ui_version,
ui_config=json.dumps(merged_ui_config),
oauth_config=json.dumps(oauth_config),
nonce_attr=nonce_attr,
)
return Response(html)

Expand Down
2 changes: 1 addition & 1 deletion pyramid_openapi3/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@

<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/${ui_version}/swagger-ui-bundle.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/${ui_version}/swagger-ui-standalone-preset.js"> </script>
<script>
<script${nonce_attr}>
window.onload = function() {
const uiConfig = ${ui_config};
Object.assign(uiConfig, {
Expand Down
60 changes: 60 additions & 0 deletions pyramid_openapi3/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,3 +760,63 @@ def test_cookie_parameters() -> None:
response = view(context, request)

assert response.json == "foo"


def test_add_explorer_view_with_csp_nonce() -> None:
"""Test explorer view includes CSP nonce when available on request."""
with testConfig() as config:
config.include("pyramid_openapi3")

with tempfile.NamedTemporaryFile() as document:
document.write(MINIMAL_DOCUMENT)
document.seek(0)

config.pyramid_openapi3_spec(
document.name, route="/foo.yaml", route_name="foo_api_spec"
)

config.pyramid_openapi3_add_explorer()
request = config.registry.queryUtility(
IRouteRequest, name="pyramid_openapi3.explorer"
)
view = config.registry.adapters.registered(
(IViewClassifier, request, Interface), IView, name=""
)

# Test with CSP nonce
dummy_request = DummyRequest(config=config)
dummy_request.csp_nonce = "test-nonce-123"
response = view(request=dummy_request, context=None)

assert b'<script nonce="test-nonce-123">' in response.body
assert b"<title>Swagger UI</title>" in response.body


def test_add_explorer_view_without_csp_nonce() -> None:
"""Test explorer view works normally when no CSP nonce is present."""
with testConfig() as config:
config.include("pyramid_openapi3")

with tempfile.NamedTemporaryFile() as document:
document.write(MINIMAL_DOCUMENT)
document.seek(0)

config.pyramid_openapi3_spec(
document.name, route="/foo.yaml", route_name="foo_api_spec"
)

config.pyramid_openapi3_add_explorer()
request = config.registry.queryUtility(
IRouteRequest, name="pyramid_openapi3.explorer"
)
view = config.registry.adapters.registered(
(IViewClassifier, request, Interface), IView, name=""
)

# Test without CSP nonce
dummy_request = DummyRequest(config=config)
response = view(request=dummy_request, context=None)

assert b"<script>" in response.body
assert b"nonce=" not in response.body
assert b"<title>Swagger UI</title>" in response.body