Spec improvements

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-09-27 09:41:36 +02:00
parent cc3106e425
commit 41f086e2c9
21 changed files with 299 additions and 90 deletions

View File

@@ -62,9 +62,9 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
static_url = Mobilizon.Web.Endpoint.static_url()
websocket_url = Mobilizon.Web.Endpoint.websocket_url()
img_src = [@img_src | get_csp_config(:img_src, options)]
img_src = [@img_src] ++ [get_csp_config(:img_src, options)]
media_src = [@media_src | get_csp_config(:media_src, options)]
media_src = [@media_src] ++ [get_csp_config(:media_src, options)]
connect_src = [
@connect_src,
@@ -85,22 +85,22 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
]
end
script_src = [script_src | get_csp_config(:script_src, options)]
script_src = [script_src] ++ [get_csp_config(:script_src, options)]
style_src =
if Config.get(:env) == :dev, do: [@style_src | "'unsafe-inline' "], else: @style_src
style_src = [style_src | get_csp_config(:style_src, options)]
style_src = [style_src] ++ [get_csp_config(:style_src, options)]
font_src = [@font_src | get_csp_config(:font_src, options)]
font_src = [@font_src] ++ [get_csp_config(:font_src, options)]
frame_src = if Config.get(:env) == :dev, do: "frame-src 'self' ", else: "frame-src 'none' "
frame_src = [frame_src | get_csp_config(:frame_src, options)]
frame_src = [frame_src] ++ [get_csp_config(:frame_src, options)]
frame_ancestors =
if Config.get(:env) == :dev, do: "frame-ancestors 'self' ", else: "frame-ancestors 'none' "
frame_ancestors = [frame_ancestors | get_csp_config(:frame_ancestors, options)]
frame_ancestors = [frame_ancestors] ++ [get_csp_config(:frame_ancestors, options)]
insecure = if scheme == "https", do: "upgrade-insecure-requests"

View File

@@ -11,8 +11,10 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlug do
import Plug.Conn, only: [assign: 3]
alias Mobilizon.Web.Gettext, as: GettextBackend
@spec init(any()) :: nil
def init(_), do: nil
@spec call(Plug.Conn.t(), any()) :: Plug.Conn.t()
def call(conn, _) do
locale =
[
@@ -29,17 +31,22 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlug do
assign(conn, :locale, locale)
end
@spec supported_locale?(String.t()) :: boolean()
defp supported_locale?(locale) do
GettextBackend
|> Gettext.known_locales()
|> Enum.member?(locale)
end
@spec default_locale :: String.t()
defp default_locale do
Keyword.get(Mobilizon.Config.instance_config(), :default_language, "en")
end
@spec determine_best_locale(String.t()) :: String.t()
@doc """
Determine the best available locale for a given locale ID
"""
@spec determine_best_locale(String.t()) :: String.t() | nil
def determine_best_locale(locale) when is_binary(locale) do
locale = String.trim(locale)
locales = Gettext.known_locales(GettextBackend)
@@ -58,5 +65,6 @@ defmodule Mobilizon.Web.Plugs.SetLocalePlug do
def determine_best_locale(_), do: nil
# Keep only the first part of the locale
@spec split_locale(String.t()) :: String.t()
defp split_locale(locale), do: locale |> String.split("_", trim: true, parts: 2) |> hd
end

View File

@@ -165,6 +165,11 @@ defmodule Mobilizon.Web.ReverseProxy do
|> halt()
end
@spec request(String.t(), String.t(), list(tuple()), Keyword.t()) ::
{:ok, 200 | 206 | 304, list(tuple()), any()}
| {:ok, 200 | 206 | 304, list(tuple())}
| {:error, {:invalid_http_response, pos_integer()}}
| {:error, any()}
defp request(method, url, headers, hackney_opts) do
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
method = method |> String.downcase() |> String.to_existing_atom()
@@ -184,6 +189,8 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
@spec response(Plug.Conn.t(), any(), String.t(), pos_integer(), list(tuple()), Keyword.t()) ::
Plug.Conn.t()
defp response(conn, client, url, status, headers, opts) do
result =
conn
@@ -209,18 +216,26 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
@spec chunk_reply(
Plug.Conn.t(),
any(),
Keyword.t(),
non_neg_integer(),
non_neg_integer() | :no_duration_limit
) ::
{:ok, Plug.Conn.t()} | {:error, any(), Plug.Conn.t()}
defp chunk_reply(conn, client, opts) do
chunk_reply(conn, client, opts, 0, 0)
end
defp chunk_reply(conn, client, opts, sent_so_far, duration) do
with {:ok, duration} <-
with {:ok, {duration, now}} <-
check_read_duration(
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
{:ok, data} <- @hackney.stream_body(client),
{:ok, duration} <- increase_read_duration(duration),
{:ok, duration} <- increase_read_duration({duration, now}),
sent_so_far = sent_so_far + byte_size(data),
:ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
{:ok, conn} <- chunk(conn, data) do
@@ -231,6 +246,8 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
@spec head_response(Plug.Conn.t(), any(), pos_integer(), list(tuple()), Keyword.t()) ::
Plug.Conn.t() | no_return()
defp head_response(conn, _url, code, headers, opts) do
conn
|> put_resp_headers(build_resp_headers(headers, opts))
@@ -238,6 +255,8 @@ defmodule Mobilizon.Web.ReverseProxy do
end
# sobelow_skip ["XSS.SendResp"]
@spec error_or_redirect(Plug.Conn.t(), String.t(), pos_integer(), String.t(), Keyword.t()) ::
Plug.Conn.t()
defp error_or_redirect(conn, url, code, body, opts) do
if Keyword.get(opts, :redirect_on_failure, false) do
conn
@@ -250,12 +269,14 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
@spec downcase_headers(list(tuple())) :: list(tuple())
defp downcase_headers(headers) do
Enum.map(headers, fn {k, v} ->
{String.downcase(k), v}
end)
end
@spec get_content_type(list(tuple())) :: String.t()
defp get_content_type(headers) do
{_, content_type} =
List.keyfind(headers, "content-type", 0, {"content-type", "application/octet-stream"})
@@ -264,12 +285,14 @@ defmodule Mobilizon.Web.ReverseProxy do
content_type
end
@spec put_resp_headers(Plug.Conn.t(), list(tuple())) :: Plug.Conn.t()
defp put_resp_headers(conn, headers) do
Enum.reduce(headers, conn, fn {k, v}, conn ->
put_resp_header(conn, k, v)
end)
end
@spec build_req_headers(list(tuple()), Keyword.t()) :: list(tuple())
defp build_req_headers(headers, opts) do
headers
|> downcase_headers()
@@ -290,6 +313,7 @@ defmodule Mobilizon.Web.ReverseProxy do
end).()
end
@spec build_resp_headers(list(tuple()), Keyword.t()) :: list(tuple())
defp build_resp_headers(headers, opts) do
headers
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
@@ -298,6 +322,7 @@ defmodule Mobilizon.Web.ReverseProxy do
|> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).()
end
@spec build_resp_cache_headers(list(tuple()), Keyword.t()) :: list(tuple())
defp build_resp_cache_headers(headers, _opts) do
has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end)
has_cache_control? = List.keymember?(headers, "cache-control", 0)
@@ -321,6 +346,7 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
@spec build_resp_content_disposition_header(list(tuple()), Keyword.t()) :: list(tuple())
defp build_resp_content_disposition_header(headers, opts) do
opt = Keyword.get(opts, :inline_content_types, @inline_content_types)
@@ -359,6 +385,8 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
@spec header_length_constraint(list(tuple()), non_neg_integer()) ::
:ok | {:error, :body_too_large}
defp header_length_constraint(headers, limit) when is_integer(limit) and limit > 0 do
with {_, size} <- List.keyfind(headers, "content-length", 0),
{size, _} <- Integer.parse(size),
@@ -375,15 +403,16 @@ defmodule Mobilizon.Web.ReverseProxy do
defp header_length_constraint(_, _), do: :ok
@spec body_size_constraint(integer(), integer()) :: :ok | {:error, :body_too_large}
defp body_size_constraint(size, limit) when is_integer(limit) and limit > 0 and size >= limit do
{:error, :body_too_large}
end
defp body_size_constraint(_, _), do: :ok
@spec check_read_duration(any(), integer()) ::
@spec check_read_duration(any(), integer() | :no_duration_limit) ::
{:ok, {integer(), integer()}}
| {:ok, :no_duration_limit, :no_duration_limit}
| {:ok, {:no_duration_limit, :no_duration_limit}}
| {:error, :read_duration_exceeded}
defp check_read_duration(duration, max)
when is_integer(duration) and is_integer(max) and max > 0 do
@@ -394,8 +423,12 @@ defmodule Mobilizon.Web.ReverseProxy do
end
end
defp check_read_duration(_, _), do: {:ok, :no_duration_limit, :no_duration_limit}
defp check_read_duration(_, _), do: {:ok, {:no_duration_limit, :no_duration_limit}}
@spec increase_read_duration(
{previous_duration :: pos_integer | :no_duration_limit,
started :: pos_integer | :no_duration_limit}
) :: {:ok, pos_integer()} | {:ok, :no_duration_limit}
defp increase_read_duration({previous_duration, started})
when is_integer(previous_duration) and is_integer(started) do
duration = :erlang.system_time(:millisecond) - started
@@ -403,9 +436,10 @@ defmodule Mobilizon.Web.ReverseProxy do
end
defp increase_read_duration(_) do
{:ok, :no_duration_limit, :no_duration_limit}
{:ok, :no_duration_limit}
end
@spec filename(String.t()) :: String.t() | nil
def filename(url_or_path) do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end

View File

@@ -17,6 +17,7 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
@json_ld_header Utils.make_json_ld_header()
@selected_member_roles ~w(creator administrator moderator member)a
@spec render(String.t(), map()) :: map()
def render("actor.json", %{actor: actor}) do
actor
|> Convertible.model_to_as()

View File

@@ -3,6 +3,7 @@ defmodule Mobilizon.Web.ActivityPub.ObjectView do
alias Mobilizon.Federation.ActivityPub.{Activity, Utils}
@spec render(String.t(), map()) :: map()
def render("activity.json", %{activity: %Activity{local: local, data: data} = activity}) do
%{
"id" => data["id"],

View File

@@ -8,6 +8,7 @@ defmodule Mobilizon.Web.AuthView do
alias Phoenix.HTML.Tag
import Mobilizon.Web.Views.Utils
@spec render(String.t(), map()) :: String.t() | Plug.Conn.t()
def render("callback.html", %{
conn: conn,
access_token: access_token,

View File

@@ -8,6 +8,7 @@ defmodule Mobilizon.Web.ErrorHelpers do
@doc """
Translates an error message using gettext.
"""
@spec translate_error({msg :: String.t(), opts :: map()}) :: String.t()
def translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We

View File

@@ -6,6 +6,7 @@ defmodule Mobilizon.Web.ErrorView do
alias Mobilizon.Service.Metadata.Instance
import Mobilizon.Web.Views.Utils
@spec render(String.t(), map()) :: map() | String.t() | Plug.Conn.t()
def render("404.html", %{conn: conn}) do
with tags <- Instance.build_tags(),
{:ok, html} <- inject_tags(tags, get_locale(conn)) do

View File

@@ -8,6 +8,7 @@ defmodule Mobilizon.Web.JsonLD.ObjectView do
alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.JsonLD.ObjectView
@spec render(String.t(), map()) :: map()
def render("group.json", %{group: %Actor{} = group}) do
%{
"@context" => "http://schema.org",
@@ -93,6 +94,7 @@ defmodule Mobilizon.Web.JsonLD.ObjectView do
}
end
@spec render_location(map()) :: map() | nil
defp render_location(%{physical_address: %Address{} = address}),
do: render_one(address, ObjectView, "place.json", as: :address)

View File

@@ -19,6 +19,8 @@ defmodule Mobilizon.Web.PageView do
alias Mobilizon.Federation.ActivityStream.Convertible
import Mobilizon.Web.Views.Utils
@doc false
@spec render(String.t(), %{conn: Plug.Conn.t()}) :: map() | String.t() | Plug.Conn.t()
def render("actor.activity-json", %{conn: %{assigns: %{object: %Actor{} = actor}}}) do
actor
|> Convertible.model_to_as()