Various refactoring and typespec improvements

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-09-24 16:46:42 +02:00
parent d235653876
commit 1893d9f55b
142 changed files with 1854 additions and 1297 deletions

View File

@@ -25,7 +25,7 @@ defmodule Mobilizon.Web.Auth.Context do
defp set_user_information_in_context(conn) do
context = %{ip: conn.remote_ip |> :inet.ntoa() |> to_string()}
user_agent = Plug.Conn.get_req_header(conn, "user-agent") |> List.first()
user_agent = conn |> Plug.Conn.get_req_header("user-agent") |> List.first()
{conn, context} =
case Guardian.Plug.current_resource(conn) do
@@ -41,7 +41,7 @@ defmodule Mobilizon.Web.Auth.Context do
},
query_string: conn.query_string,
env: %{
REQUEST_ID: Plug.Conn.get_resp_header(conn, "x-request-id") |> List.first(),
REQUEST_ID: conn |> Plug.Conn.get_resp_header("x-request-id") |> List.first(),
SERVER_NAME: conn.host
}
})

View File

@@ -23,6 +23,8 @@ defmodule Mobilizon.Web.Auth.Guardian do
{:error, :unknown_resource}
end
@spec resource_from_claims(any) ::
{:error, :invalid_id | :no_result | :no_claims} | {:ok, Mobilizon.Users.User.t()}
def resource_from_claims(%{"sub" => "User:" <> uid_str}) do
Logger.debug(fn -> "Receiving claim for user #{uid_str}" end)
@@ -40,7 +42,7 @@ defmodule Mobilizon.Web.Auth.Guardian do
end
def resource_from_claims(_) do
{:error, :reason_for_error}
{:error, :no_claims}
end
def after_encode_and_sign(resource, claims, token, _options) do

View File

@@ -23,15 +23,18 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
@spec get_actor_by_name(String.t()) ::
{:commit, ActorModel.t()} | {:ignore, nil}
def get_actor_by_name(name) do
Cachex.fetch(@cache, "actor_" <> name, fn "actor_" <> name ->
case Actor.find_or_make_actor_from_nickname(name) do
{:ok, %ActorModel{} = actor} ->
{:commit, actor}
Cachex.fetch(@cache, "actor_" <> name, &do_get_actor/1)
end
nil ->
{:ignore, nil}
end
end)
@spec do_get_actor(String.t()) :: {:commit, Actor.t()} | {:ignore, nil}
defp do_get_actor("actor_" <> name) do
case Actor.find_or_make_actor_from_nickname(name) do
{:ok, %ActorModel{} = actor} ->
{:commit, actor}
{:error, _err} ->
{:ignore, nil}
end
end
@doc """
@@ -179,7 +182,7 @@ defmodule Mobilizon.Web.Cache.ActivityPub do
Gets a member by its UUID, with all associations loaded.
"""
@spec get_member_by_uuid_with_preload(String.t()) ::
{:commit, Todo.t()} | {:ignore, nil}
{:commit, Member.t()} | {:ignore, nil}
def get_member_by_uuid_with_preload(uuid) do
Cachex.fetch(@cache, "member_" <> uuid, fn "member_" <> uuid ->
case Actors.get_member(uuid) do

View File

@@ -3,7 +3,12 @@ defmodule Mobilizon.Web.Cache do
Facade module which provides access to all cached data.
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Events.Event
alias Mobilizon.Posts.Post
alias Mobilizon.Resources.Resource
alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Web.Cache.ActivityPub
import Mobilizon.Service.Guards, only: [is_valid_string: 1]
@@ -20,15 +25,26 @@ defmodule Mobilizon.Web.Cache do
Enum.each(@caches, &Cachex.del(&1, "actor_" <> preferred_username))
end
@spec get_actor_by_name(binary) :: {:commit, Actor.t()} | {:ignore, nil}
defdelegate get_actor_by_name(name), to: ActivityPub
@spec get_local_actor_by_name(binary) :: {:commit, Actor.t()} | {:ignore, nil}
defdelegate get_local_actor_by_name(name), to: ActivityPub
@spec get_public_event_by_uuid_with_preload(binary) :: {:commit, Event.t()} | {:ignore, nil}
defdelegate get_public_event_by_uuid_with_preload(uuid), to: ActivityPub
@spec get_comment_by_uuid_with_preload(binary) :: {:commit, Comment.t()} | {:ignore, nil}
defdelegate get_comment_by_uuid_with_preload(uuid), to: ActivityPub
@spec get_resource_by_uuid_with_preload(binary) :: {:commit, Resource.t()} | {:ignore, nil}
defdelegate get_resource_by_uuid_with_preload(uuid), to: ActivityPub
@spec get_todo_list_by_uuid_with_preload(binary) :: {:commit, TodoList.t()} | {:ignore, nil}
defdelegate get_todo_list_by_uuid_with_preload(uuid), to: ActivityPub
@spec get_todo_by_uuid_with_preload(binary) :: {:commit, Todo.t()} | {:ignore, nil}
defdelegate get_todo_by_uuid_with_preload(uuid), to: ActivityPub
@spec get_member_by_uuid_with_preload(binary) :: {:commit, Member.t()} | {:ignore, nil}
defdelegate get_member_by_uuid_with_preload(uuid), to: ActivityPub
@spec get_post_by_slug_with_preload(binary) :: {:commit, Post.t()} | {:ignore, nil}
defdelegate get_post_by_slug_with_preload(slug), to: ActivityPub
@spec get_discussion_by_slug_with_preload(binary) :: {:commit, Discussion.t()} | {:ignore, nil}
defdelegate get_discussion_by_slug_with_preload(slug), to: ActivityPub
@spec get_relay :: {:commit, Actor.t()} | {:ignore, nil}
defdelegate get_relay, to: ActivityPub
end

View File

@@ -24,6 +24,7 @@ defmodule Mobilizon.Web.ActivityPubController do
plug(Mobilizon.Web.Plugs.Federating when action in [:inbox, :relay])
plug(:relay_active? when action in [:relay])
@spec relay_active?(Plug.Conn.t(), any()) :: Plug.Conn.t()
def relay_active?(conn, _) do
if Config.get([:instance, :allow_relay]) do
conn
@@ -35,34 +36,42 @@ defmodule Mobilizon.Web.ActivityPubController do
end
end
@spec following(Plug.Conn.t(), map()) :: Plug.Conn.t()
def following(conn, args) do
actor_collection(conn, "following", args)
end
@spec followers(Plug.Conn.t(), map()) :: Plug.Conn.t()
def followers(conn, args) do
actor_collection(conn, "followers", args)
end
@spec members(Plug.Conn.t(), map()) :: Plug.Conn.t()
def members(conn, args) do
actor_collection(conn, "members", args)
end
@spec resources(Plug.Conn.t(), map()) :: Plug.Conn.t()
def resources(conn, args) do
actor_collection(conn, "resources", args)
end
@spec posts(Plug.Conn.t(), map()) :: Plug.Conn.t()
def posts(conn, args) do
actor_collection(conn, "posts", args)
end
@spec todos(Plug.Conn.t(), map()) :: Plug.Conn.t()
def todos(conn, args) do
actor_collection(conn, "todos", args)
end
@spec events(Plug.Conn.t(), map()) :: Plug.Conn.t()
def events(conn, args) do
actor_collection(conn, "events", args)
end
@spec discussions(Plug.Conn.t(), map()) :: Plug.Conn.t()
def discussions(conn, args) do
actor_collection(conn, "discussions", args)
end
@@ -70,38 +79,45 @@ defmodule Mobilizon.Web.ActivityPubController do
@ok_statuses [:ok, :commit]
@spec member(Plug.Conn.t(), map) :: {:error, :not_found} | Plug.Conn.t()
def member(conn, %{"uuid" => uuid}) do
with {status, %Member{parent: %Actor{} = group, actor: %Actor{domain: nil} = _actor} = member}
when status in @ok_statuses <-
Cache.get_member_by_uuid_with_preload(uuid),
actor <- Map.get(conn.assigns, :actor),
true <- actor_applicant_group_member?(group, actor) do
json(
conn,
ActorView.render("member.json", %{
member: member,
actor_applicant: actor
})
)
else
case Cache.get_member_by_uuid_with_preload(uuid) do
{status, %Member{parent: %Actor{} = group, actor: %Actor{domain: nil} = _actor} = member}
when status in @ok_statuses ->
actor = Map.get(conn.assigns, :actor)
if actor_applicant_group_member?(group, actor) do
json(
conn,
ActorView.render("member.json", %{
member: member,
actor_applicant: actor
})
)
else
not_found(conn)
end
{status, %Member{actor: %Actor{url: domain}, parent: %Actor{} = group, url: url}}
when status in @ok_statuses and not is_nil(domain) ->
with actor <- Map.get(conn.assigns, :actor),
true <- actor_applicant_group_member?(group, actor) do
actor = Map.get(conn.assigns, :actor)
if actor_applicant_group_member?(group, actor) do
redirect(conn, external: url)
else
_ ->
conn
|> put_status(404)
|> json("Not found")
not_found(conn)
end
_ ->
conn
|> put_status(404)
|> json("Not found")
not_found(conn)
end
end
@spec not_found(Plug.Conn.t()) :: Plug.Conn.t()
defp not_found(conn) do
conn
|> put_status(404)
|> json("Not found")
end
def outbox(conn, args) do
actor_collection(conn, "outbox", args)
end
@@ -201,6 +217,7 @@ defmodule Mobilizon.Web.ActivityPubController do
end
end
@spec actor_applicant_group_member?(Actor.t(), Actor.t() | nil) :: boolean()
defp actor_applicant_group_member?(%Actor{}, nil), do: false
defp actor_applicant_group_member?(%Actor{id: group_id}, %Actor{id: actor_applicant_id}),

60
lib/web/email/actor.ex Normal file
View File

@@ -0,0 +1,60 @@
defmodule Mobilizon.Web.Email.Actor do
@moduledoc """
Handles emails sent about actors status.
"""
use Bamboo.Phoenix, view: Mobilizon.Web.EmailView
import Bamboo.Phoenix
import Mobilizon.Web.Gettext
alias Mobilizon.Actors.Actor
alias Mobilizon.{Config, Users}
alias Mobilizon.Events.{Event, Participant}
alias Mobilizon.Users.User
alias Mobilizon.Web.Email
@doc """
Send a notification to participants from events organized by an actor that is going to be suspended
"""
@spec send_notification_event_participants_from_suspension(Participant.t(), Actor.t()) ::
:ok
def send_notification_event_participants_from_suspension(
%Participant{
actor: %Actor{user_id: nil}
},
_suspended
),
do: :ok
def send_notification_event_participants_from_suspension(%Participant{role: role}, _suspended)
when role not in [:participant, :moderator, :administrator],
do: :ok
def send_notification_event_participants_from_suspension(
%Participant{
actor: %Actor{user_id: user_id},
event: %Event{} = event,
role: member_role
},
%Actor{} = suspended
) do
with %User{email: email, locale: locale} <- Users.get_user!(user_id) do
Gettext.put_locale(locale)
instance = Config.instance_name()
subject = gettext("Your participation to %{event} has been cancelled!", event: event.title)
Email.base_email(to: email, subject: subject)
|> assign(:locale, locale)
|> assign(:actor, suspended)
|> assign(:event, event)
|> assign(:role, member_role)
|> assign(:subject, subject)
|> assign(:instance, instance)
|> render(:actor_suspension_participants)
|> Email.Mailer.send_email_later()
:ok
end
end
end

View File

@@ -79,6 +79,7 @@ defmodule Mobilizon.Web.Email.Group do
# TODO : def send_confirmation_to_inviter()
@member_roles [:administrator, :moderator, :member]
@spec send_group_suspension_notification(Member.t()) :: :ok
def send_group_suspension_notification(%Member{actor: %Actor{user_id: nil}}), do: :ok
def send_group_suspension_notification(%Member{role: role}) when role not in @member_roles,
@@ -112,64 +113,4 @@ defmodule Mobilizon.Web.Email.Group do
:ok
end
end
def send_group_deletion_notification(%Member{actor: %Actor{user_id: nil}}, _author), do: :ok
def send_group_deletion_notification(%Member{role: role}, _author)
when role not in @member_roles,
do: :ok
@spec send_group_deletion_notification(Member.t(), Actor.t()) :: :ok
def send_group_deletion_notification(
%Member{
actor: %Actor{user_id: user_id, id: actor_id} = member
},
%Actor{id: author_id} = author
) do
with %User{email: email, locale: locale} <- Users.get_user!(user_id),
{:member_not_author, true} <- {:member_not_author, author_id !== actor_id} do
do_send_group_deletion_notification(member, author: author, email: email, locale: locale)
else
# Skip if it's the author itself
{:member_not_author, _} ->
:ok
end
end
@spec send_group_deletion_notification(Member.t()) :: :ok
def send_group_deletion_notification(%Member{actor: %Actor{user_id: user_id}} = member) do
case Users.get_user!(user_id) do
%User{email: email, locale: locale} ->
do_send_group_deletion_notification(member, email: email, locale: locale)
end
end
defp do_send_group_deletion_notification(
%Member{role: member_role, parent: %Actor{domain: nil} = group},
options
) do
locale = Keyword.get(options, :locale)
Gettext.put_locale(locale)
instance = Config.instance_name()
author = Keyword.get(options, :author)
subject =
gettext(
"The group %{group} has been deleted on %{instance}",
group: group.name,
instance: instance
)
Email.base_email(to: Keyword.get(options, :email), subject: subject)
|> assign(:locale, locale)
|> assign(:group, group)
|> assign(:role, member_role)
|> assign(:subject, subject)
|> assign(:instance, instance)
|> assign(:author, author)
|> render(:group_deletion)
|> Email.Mailer.send_email_later()
:ok
end
end

View File

@@ -199,14 +199,14 @@ defmodule Mobilizon.Web.Email.User do
:ok
_ ->
case Timex.before?(
Timex.shift(Map.get(user, key), hours: 1),
case DateTime.compare(
DateTime.add(Map.get(user, key), 3600),
DateTime.utc_now() |> DateTime.truncate(:second)
) do
true ->
:lt ->
:ok
false ->
_ ->
{:error, :email_too_soon}
end
end

View File

@@ -117,7 +117,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
|> to_string()
end
@spec add_csp_param(list(), list(String.t()) | String.t() | nil) :: list()
@spec add_csp_param(iodata(), iodata() | nil) :: list()
defp add_csp_param(csp_iodata, nil), do: csp_iodata
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
@@ -132,7 +132,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSecurityPlug do
defp maybe_send_sts_header(conn, false), do: conn
@spec get_csp_config(atom(), Keyword.t()) :: String.t()
@spec get_csp_config(atom(), Keyword.t()) :: iodata()
defp get_csp_config(type, options) do
options
|> Keyword.get(type, Config.get([:http_security, :csp_policy, type]))

View File

@@ -8,7 +8,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
Plug to check HTTP Signatures on every incoming request
"""
import Plug.Conn
import Plug.Conn, only: [get_req_header: 2, put_req_header: 3, assign: 3]
require Logger
@@ -22,48 +22,61 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
end
def call(conn, _opts) do
case get_req_header(conn, "signature") do
[signature | _] ->
if signature do
# set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
signature = conn |> get_req_header("signature") |> List.first()
conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
conn
end
if is_nil(signature) do
Logger.debug("No signature header!")
conn
else
# set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
signature_valid = HTTPSignatures.validate_conn(conn)
Logger.debug("Is signature valid ? #{inspect(signature_valid)}")
date_valid = date_valid?(conn)
assign(conn, :valid_signature, signature_valid && date_valid)
conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
Logger.debug("No signature header!")
conn
end
_ ->
conn
signature_valid = HTTPSignatures.validate_conn(conn)
Logger.debug("Is signature valid ? #{inspect(signature_valid)}")
date_valid = date_valid?(conn)
assign(conn, :valid_signature, signature_valid && date_valid)
end
end
@spec date_valid?(Plug.Conn.t()) :: boolean()
defp date_valid?(conn) do
with [date | _] <- get_req_header(conn, "date") || [""],
{:ok, date} <- Timex.parse(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") do
Timex.diff(date, DateTime.utc_now(), :hours) <= 12 &&
Timex.diff(date, DateTime.utc_now(), :hours) >= -12
date = conn |> get_req_header("date") |> List.first()
if is_nil(date) do
false
else
_ -> false
case Timex.parse(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") do
{:ok, %NaiveDateTime{} = date} ->
date
|> DateTime.from_naive!("Etc/UTC")
|> date_diff_ok?()
{:ok, %DateTime{} = date} ->
date_diff_ok?(date)
{:error, _err} ->
false
end
end
end
@spec date_diff_ok?(DateTime.t()) :: boolean()
defp date_diff_ok?(%DateTime{} = date) do
DateTime.diff(date, DateTime.utc_now()) <= 12 * 3600 &&
DateTime.diff(date, DateTime.utc_now()) >= -12 * 3600
end
end

View File

@@ -10,7 +10,7 @@
<tr>
<td bgcolor="#ffffff" align="center" valign="top" style="padding: 40px 20px 20px 20px; border-radius: 4px 4px 0px 0px; color: #3A384C; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 48px; font-weight: 400; line-height: 48px;">
<h1 style="font-size: 48px; font-weight: 400; margin: 0;">
<%= gettext "The group %{group} was deleted on %{instance}!", group: (@group.name || @group.preferred_username), instance: @instance %>
<%= gettext "Your participation to %{event} has been cancelled!", event: (@event.title), instance: @instance %>
</h1>
</td>
</tr>
@@ -35,9 +35,7 @@
<tr>
<td bgcolor="#ffffff" align="left" style="padding: 20px 30px 0px 30px; color: #474467; font-family: 'Roboto', Helvetica, Arial, sans-serif; font-size: 18px; font-weight: 400; line-height: 25px;" >
<p style="margin: 0;">
<%= gettext "The administrator %{author} deleted group %{group}. All of the group's events, discussions, posts and todos have been deleted.",
author: Mobilizon.Actors.Actor.display_name_and_username(@author),
group: Mobilizon.Actors.Actor.display_name_and_username(@group) %>
<%= gettext "Your instance's moderation team has decided to suspend %{actor_name} (%{actor_address}). All of their events have been removed and your participation cancelled.", group_name: @actor.name || @actor.preferred_username, actor_address: if @actor.domain, do: "@#{@actor.preferred_username}@#{@actor.domain}", else: "@#{@actor.preferred_username}" %>
</p>
</td>
</tr>

View File

@@ -0,0 +1,3 @@
<%= gettext "Your participation to %{event} has been cancelled!", event: (@event.title), instance: @instance %>
==
<%= gettext "Your instance's moderation team has decided to suspend %{actor_name} (%{actor_address}). All of their events have been removed and your participation cancelled.", group_name: @actor.name || @actor.preferred_username, actor_address: if @actor.domain, do: "@#{@actor.preferred_username}@#{@actor.domain}", else: "@#{@actor.preferred_username}" %>

View File

@@ -1,5 +0,0 @@
<%= gettext "The group %{group} was deleted on %{instance}!", group: (@group.name || @group.preferred_username), instance: @instance %>
==
<%= gettext "The administrator %{author} deleted group %{group}. All of the group's events, discussions, posts and todos have been deleted.",
author: Mobilizon.Actors.Actor.display_name_and_username(@author),
group: Mobilizon.Actors.Actor.display_name_and_username(@group) %>

View File

@@ -11,12 +11,22 @@ defmodule Mobilizon.Web.Upload.MIME do
@read_bytes 35
@spec file_mime_type(String.t(), String.t()) ::
{:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
@spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, any()} | :error
{:ok, content_type :: String.t(), filename :: String.t()}
| {:error, :unknown_mime | :failed_to_fix_extension}
@spec file_mime_type(String.t()) :: {:ok, String.t()} | {:error, :unknown_mime}
def file_mime_type(path, filename) do
with {:ok, content_type} <- file_mime_type(path),
filename when is_binary(filename) <- fix_extension(filename, content_type) do
{:ok, content_type, filename}
case file_mime_type(path) do
{:ok, content_type} ->
case fix_extension(filename, content_type) do
{:error, :failed_to_fix_extension} ->
{:error, :failed_to_fix_extension}
filename when is_binary(filename) ->
{:ok, content_type, filename}
end
{:error, :unknown_mime} ->
{:error, :unknown_mime}
end
end
@@ -33,12 +43,12 @@ defmodule Mobilizon.Web.Upload.MIME do
end
end
@spec bin_mime_type(binary()) :: {:ok, String.t()} | :error
@spec bin_mime_type(binary()) :: {:ok, String.t()} | {:error, :unknown_mime}
def bin_mime_type(<<head::binary-size(@read_bytes), _::binary>>) do
{:ok, check_mime_type(head)}
end
def bin_mime_type(_), do: :error
def bin_mime_type(_), do: {:error, :unknown_mime}
def mime_type(<<_::binary>>), do: {:ok, @default}
@@ -67,8 +77,9 @@ defmodule Mobilizon.Web.Upload.MIME do
end
end
defp fix_extension(_, _), do: :error
defp fix_extension(_, _), do: {:error, :failed_to_fix_extension}
@spec check_mime_type(binary) :: String.t()
defp check_mime_type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, _::binary>>) do
"image/png"
end

View File

@@ -79,7 +79,7 @@ defmodule Mobilizon.Web.Upload do
}
@spec store(source, options :: [option()]) ::
{:ok, map()} | {:error, String.t()} | {:error, atom()}
{:ok, t()} | {:error, String.t()} | {:error, atom()}
def store(upload, opts \\ []) do
opts = get_opts(opts)
@@ -90,7 +90,7 @@ defmodule Mobilizon.Web.Upload do
|> perform_filter_and_put_file(opts)
{:error, error} ->
error
{:error, error}
end
end
@@ -169,28 +169,38 @@ defmodule Mobilizon.Web.Upload do
{Config.get!([:instance, :upload_limit]), nil}
end
activity_type = Keyword.get(opts, :activity_type, activity_type)
size_limit = Keyword.get(opts, :size_limit, size_limit)
uploader = Keyword.get(opts, :uploader, Config.get([__MODULE__, :uploader]))
filters = Keyword.get(opts, :filters, Config.get([__MODULE__, :filters]))
description = Keyword.get(opts, :description)
allow_list_mime_types =
Keyword.get(
opts,
:allow_list_mime_types,
Config.get([__MODULE__, :allow_list_mime_types])
)
base_url =
Keyword.get(
opts,
:base_url,
Config.get([__MODULE__, :base_url], Endpoint.url())
)
%{
activity_type: Keyword.get(opts, :activity_type, activity_type),
size_limit: Keyword.get(opts, :size_limit, size_limit),
uploader: Keyword.get(opts, :uploader, Config.get([__MODULE__, :uploader])),
filters: Keyword.get(opts, :filters, Config.get([__MODULE__, :filters])),
description: Keyword.get(opts, :description),
allow_list_mime_types:
Keyword.get(
opts,
:allow_list_mime_types,
Config.get([__MODULE__, :allow_list_mime_types])
),
base_url:
Keyword.get(
opts,
:base_url,
Config.get([__MODULE__, :base_url], Endpoint.url())
)
activity_type: activity_type,
size_limit: size_limit,
uploader: uploader,
filters: filters,
description: description,
allow_list_mime_types: allow_list_mime_types,
base_url: base_url
}
end
@spec prepare_upload(t(), internal_options()) :: {:ok, t()}
@spec prepare_upload(t(), internal_options()) :: {:ok, t()} | {:error, atom()}
defp prepare_upload(%Plug.Upload{} = file, opts) do
with {:ok, size} <- check_file_size(file.path, opts.size_limit),
{:ok, content_type, name} <- MIME.file_mime_type(file.path, file.filename),
@@ -203,10 +213,14 @@ defmodule Mobilizon.Web.Upload do
content_type: content_type,
size: size
}}
else
{:error, err} ->
{:error, err}
end
end
@spec prepare_upload(%{body: String.t(), name: String.t()}, internal_options()) :: {:ok, t()}
@spec prepare_upload(%{body: String.t(), name: String.t()}, internal_options()) ::
{:ok, t()} | {:error, :mime_type_not_allowed}
defp prepare_upload(%{body: body, name: name} = _file, opts) do
with :ok <- check_binary_size(body, opts.size_limit),
tmp_path <- tempfile_for_image(body),
@@ -270,7 +284,7 @@ defmodule Mobilizon.Web.Upload do
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
@spec check_allowed_mime_type(String.t(), List.t()) :: :ok | {:error, :atom}
@spec check_allowed_mime_type(String.t(), List.t()) :: :ok | {:error, :mime_type_not_allowed}
defp check_allowed_mime_type(content_type, allow_list_mime_types) do
if Enum.any?(allow_list_mime_types, &(&1 == content_type)),
do: :ok,

View File

@@ -21,8 +21,8 @@ defmodule Mobilizon.Web.Upload.Uploader.Local do
@impl true
@spec put_file(Upload.t()) ::
:ok | {:ok, {:file, String.t()}} | {:error, :tempfile_no_longer_exists}
def put_file(%Upload{path: path, tempfile: tempfile}) do
{path, file} = local_path(path)
def put_file(%Upload{path: initial_path, tempfile: tempfile}) do
{path, file} = local_path(initial_path)
result_file = Path.join(path, file)
if File.exists?(result_file) do
@@ -31,23 +31,11 @@ defmodule Mobilizon.Web.Upload.Uploader.Local do
else
if File.exists?(tempfile) do
File.cp!(tempfile, result_file)
{:ok, {:file, result_file}}
{:ok, {:file, initial_path}}
else
{:error, :tempfile_no_longer_exists}
end
end
with {:result_exists, false} <- {:result_exists, File.exists?(result_file)},
{:temp_file_exists, true} <- {:temp_file_exists, File.exists?(tempfile)} do
File.cp!(tempfile, result_file)
else
{:result_exists, _} ->
# If the resulting file already exists, it's because of the Dedupe filter
:ok
{:temp_file_exists, _} ->
{:error, "Temporary file no longer exists"}
end
end
@impl true