remote follow: use HTML redirect to work around CSP issue

In Chrome, I get the following when trying to use the remote follow form:

    Refused to send form data to 'https://example.com/remote_follow'
    because it violates the following Content Security Policy directive:
    "form-action 'self'".

It seems some browsers (but notably not Firefox) apply the form-action
policy to the redirect target in addition to the initial form
submission endpoint.  See:

    https://github.com/w3c/webappsec-csp/issues/8

In that thread, this workaround is suggested.
This commit is contained in:
Kevin Wallace 2022-11-11 09:51:11 -08:00 committed by Thomas Sileo
parent 793a939046
commit 9db7bdf0fb

View file

@ -1,4 +1,5 @@
import base64 import base64
import html
import os import os
import sys import sys
import time import time
@ -38,6 +39,7 @@ from starlette.background import BackgroundTask
from starlette.datastructures import Headers from starlette.datastructures import Headers
from starlette.datastructures import MutableHeaders from starlette.datastructures import MutableHeaders
from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.responses import HTMLResponse
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from starlette.types import Message from starlette.types import Message
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware # type: ignore from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware # type: ignore
@ -254,6 +256,25 @@ class ActivityPubResponse(JSONResponse):
media_type = "application/activity+json" media_type = "application/activity+json"
class HTMLRedirectResponse(HTMLResponse):
"""
Similar to RedirectResponse, but uses a 200 response with HTML.
Needed for remote redirects on form submission endpoints,
since our CSP policy disallows remote form submission.
https://github.com/w3c/webappsec-csp/issues/8#issuecomment-810108984
"""
def __init__(
self,
url: str,
) -> None:
super().__init__(
content=f'<a href="{html.escape(url)}">Continue to remote resource</a>',
headers={"Refresh": "0;url=" + url},
)
@app.get(config.NavBarItems.NOTES_PATH) @app.get(config.NavBarItems.NOTES_PATH)
async def index( async def index(
request: Request, request: Request,
@ -961,7 +982,7 @@ async def post_remote_follow(
request: Request, request: Request,
csrf_check: None = Depends(verify_csrf_token), csrf_check: None = Depends(verify_csrf_token),
profile: str = Form(), profile: str = Form(),
) -> RedirectResponse: ) -> HTMLRedirectResponse:
if not profile.startswith("@"): if not profile.startswith("@"):
profile = f"@{profile}" profile = f"@{profile}"
@ -970,9 +991,8 @@ async def post_remote_follow(
# TODO(ts): error message to user # TODO(ts): error message to user
raise HTTPException(status_code=404) raise HTTPException(status_code=404)
return RedirectResponse( return HTMLRedirectResponse(
remote_follow_template.format(uri=ID), remote_follow_template.format(uri=ID),
status_code=302,
) )