2019-12-06 18:41:02 +01:00
|
|
|
defmodule PhilomenaWeb.ContentSecurityPolicyPlug do
|
2024-05-04 03:06:15 +02:00
|
|
|
import PhilomenaWeb.Config
|
2020-08-24 00:30:58 +02:00
|
|
|
import Plug.Conn
|
|
|
|
|
|
|
|
@allowed_sources [
|
|
|
|
:script_src,
|
|
|
|
:frame_src,
|
|
|
|
:style_src
|
|
|
|
]
|
2019-12-06 18:41:02 +01:00
|
|
|
|
2020-08-06 19:27:56 +02:00
|
|
|
def init(opts) do
|
|
|
|
opts
|
|
|
|
end
|
|
|
|
|
|
|
|
def call(conn, _opts) do
|
2019-12-06 18:41:02 +01:00
|
|
|
cdn_uri = cdn_uri()
|
|
|
|
camo_uri = camo_uri()
|
|
|
|
|
2020-08-24 00:30:58 +02:00
|
|
|
register_before_send(conn, fn conn ->
|
|
|
|
config = get_config(conn)
|
|
|
|
|
|
|
|
script_src = Keyword.get(config, :script_src, [])
|
|
|
|
style_src = Keyword.get(config, :style_src, [])
|
|
|
|
frame_src = Keyword.get(config, :frame_src, nil)
|
|
|
|
|
|
|
|
csp_config = [
|
2021-03-17 01:24:58 +01:00
|
|
|
{:default_src, ["'self'"]},
|
2024-04-30 19:13:46 +02:00
|
|
|
{:script_src, [default_script_src() | script_src]},
|
|
|
|
{:connect_src, [default_connect_src()]},
|
|
|
|
{:style_src, [default_style_src() | style_src]},
|
2020-08-24 00:30:58 +02:00
|
|
|
{:object_src, ["'none'"]},
|
|
|
|
{:frame_ancestors, ["'none'"]},
|
|
|
|
{:frame_src, frame_src || ["'none'"]},
|
|
|
|
{:form_action, ["'self'"]},
|
|
|
|
{:manifest_src, ["'self'"]},
|
2021-10-06 03:31:50 +02:00
|
|
|
{:img_src, ["'self'", "blob:", "data:", cdn_uri, camo_uri]},
|
2024-06-23 17:52:20 +02:00
|
|
|
{:media_src, ["'self'", "blob:", "data:", cdn_uri, camo_uri]}
|
2020-08-24 00:30:58 +02:00
|
|
|
]
|
|
|
|
|
2024-06-25 05:23:43 +02:00
|
|
|
csp_value = Enum.map_join(csp_config, "; ", &cspify_element/1)
|
2020-08-24 00:30:58 +02:00
|
|
|
|
2024-05-04 03:06:15 +02:00
|
|
|
csp_relaxed? do
|
|
|
|
if conn.status == 500 do
|
|
|
|
# Allow Plug.Debugger to function in this case
|
|
|
|
delete_resp_header(conn, "content-security-policy")
|
|
|
|
else
|
|
|
|
# Enforce CSP otherwise
|
|
|
|
put_resp_header(conn, "content-security-policy", csp_value)
|
|
|
|
end
|
2024-04-28 20:09:08 +02:00
|
|
|
else
|
|
|
|
put_resp_header(conn, "content-security-policy", csp_value)
|
|
|
|
end
|
2020-08-24 00:30:58 +02:00
|
|
|
end)
|
|
|
|
end
|
2019-12-06 18:41:02 +01:00
|
|
|
|
2020-08-24 00:30:58 +02:00
|
|
|
def permit_source(conn, key, value) when key in @allowed_sources do
|
|
|
|
conn
|
|
|
|
|> get_config()
|
2020-09-12 19:43:16 +02:00
|
|
|
|> Keyword.update(key, value, &(value ++ &1))
|
2020-08-24 00:30:58 +02:00
|
|
|
|> set_config(conn)
|
2019-12-06 18:41:02 +01:00
|
|
|
end
|
|
|
|
|
2020-08-24 00:30:58 +02:00
|
|
|
defp get_config(conn), do: conn.private[:csp] || []
|
|
|
|
defp set_config(value, conn), do: put_private(conn, :csp, value)
|
|
|
|
|
2019-12-06 18:41:02 +01:00
|
|
|
defp cdn_uri, do: Application.get_env(:philomena, :cdn_host) |> to_uri()
|
|
|
|
defp camo_uri, do: Application.get_env(:philomena, :camo_host) |> to_uri()
|
2024-04-30 19:13:46 +02:00
|
|
|
|
2024-05-04 03:06:15 +02:00
|
|
|
defp default_script_src, do: vite_hmr?(do: "'self' localhost:5173", else: "'self'")
|
2024-04-30 19:13:46 +02:00
|
|
|
|
|
|
|
defp default_connect_src,
|
2024-05-04 03:06:15 +02:00
|
|
|
do: vite_hmr?(do: "'self' localhost:5173 ws://localhost:5173", else: "'self'")
|
2024-04-30 19:13:46 +02:00
|
|
|
|
2024-05-04 03:06:15 +02:00
|
|
|
defp default_style_src, do: vite_hmr?(do: "'self' 'unsafe-inline'", else: "'self'")
|
2019-12-06 18:41:02 +01:00
|
|
|
|
|
|
|
defp to_uri(host) when host in [nil, ""], do: ""
|
|
|
|
defp to_uri(host), do: URI.to_string(%URI{scheme: "https", host: host})
|
2020-08-24 00:30:58 +02:00
|
|
|
|
|
|
|
defp cspify_element({key, value}) do
|
|
|
|
key =
|
|
|
|
key
|
|
|
|
|> Atom.to_string()
|
|
|
|
|> String.replace("_", "-")
|
|
|
|
|
|
|
|
Enum.join([key | value], " ")
|
|
|
|
end
|
2020-01-11 05:20:19 +01:00
|
|
|
end
|