HTTP Security plug: rewrite &csp_string/0
authorrinpatch <rinpatch@sdf.org>
Wed, 27 May 2020 17:27:30 +0000 (20:27 +0300)
committerrinpatch <rinpatch@sdf.org>
Wed, 27 May 2020 18:31:47 +0000 (21:31 +0300)
- Directives are now separated with ";" instead of " ;",
according to https://www.w3.org/TR/CSP2/#policy-parsing
the space is optional
- Use an IO list, which at the end gets converted to a binary as
opposed to ++ing a bunch of arrays with binaries together and joining
them to a string. I doubt it gives any significant real world advantage,
but the code is cleaner and now I can sleep at night.
- The static part of csp is pre-joined to a single binary at compile time.
Same reasoning as the last point.

lib/pleroma/plugs/http_security_plug.ex
test/plugs/http_security_plug_test.exs

index 6462797b635787d39160b192c80d857e462c1482..f9aff2faba03e93bdcbfa352d4102278a4471ed4 100644 (file)
@@ -31,7 +31,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       {"x-content-type-options", "nosniff"},
       {"referrer-policy", referrer_policy},
       {"x-download-options", "noopen"},
-      {"content-security-policy", csp_string() <> ";"}
+      {"content-security-policy", csp_string()}
     ]
 
     if report_uri do
@@ -43,23 +43,35 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
         ]
       }
 
-      headers ++ [{"reply-to", Jason.encode!(report_group)}]
+      [{"reply-to", Jason.encode!(report_group)} | headers]
     else
       headers
     end
   end
 
+  @csp_start [
+               "default-src 'none'",
+               "base-uri 'self'",
+               "frame-ancestors 'none'",
+               "style-src 'self' 'unsafe-inline'",
+               "font-src 'self'",
+               "manifest-src 'self'"
+             ]
+             |> Enum.join(";")
+             |> Kernel.<>(";")
+             |> List.wrap()
+
   defp csp_string do
     scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
     static_url = Pleroma.Web.Endpoint.static_url()
     websocket_url = Pleroma.Web.Endpoint.websocket_url()
     report_uri = Config.get([:http_security, :report_uri])
 
-    connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
+    connect_src = ["connect-src 'self' ", static_url, ?\s, websocket_url]
 
     connect_src =
       if Pleroma.Config.get(:env) == :dev do
-        connect_src <> " http://localhost:3035/"
+        [connect_src," http://localhost:3035/"]
       else
         connect_src
       end
@@ -71,26 +83,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
         "script-src 'self'"
       end
 
-    main_part = [
-      "default-src 'none'",
-      "base-uri 'self'",
-      "frame-ancestors 'none'",
-      "img-src 'self' data: blob: https:",
-      "media-src 'self' https:",
-      "style-src 'self' 'unsafe-inline'",
-      "font-src 'self'",
-      "manifest-src 'self'",
-      connect_src,
-      script_src
-    ]
-
-    report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: []
+    report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
+    insecure = if scheme == "https", do: "upgrade-insecure-requests"
+
+    @csp_start
+    |> add_csp_param("img-src 'self' data: blob: https:")
+    |> add_csp_param("media-src 'self' https:")
+    |> add_csp_param(connect_src)
+    |> add_csp_param(script_src)
+    |> add_csp_param(insecure)
+    |> add_csp_param(report)
+    |> :erlang.iolist_to_binary()
+  end
 
-    insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: []
+  defp add_csp_param(csp_iodata, nil), do: csp_iodata
 
-    (main_part ++ report ++ insecure)
-    |> Enum.join("; ")
-  end
+  defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
 
   def warn_if_disabled do
     unless Config.get([:http_security, :enabled]) do
index 84e4c274fb5b54a2a0ba9c0a470ebc3cf99fa4db..63b4d3f31fefe5a0ec8edfd43fbac8ded7ebb73a 100644 (file)
@@ -67,7 +67,7 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
 
       [csp] = Conn.get_resp_header(conn, "content-security-policy")
 
-      assert csp =~ ~r|report-uri https://endpoint.com; report-to csp-endpoint;|
+      assert csp =~ ~r|report-uri https://endpoint.com;report-to csp-endpoint;|
 
       [reply_to] = Conn.get_resp_header(conn, "reply-to")