Various refactoring and typespec improvements
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
21
lib/web/cache/activity_pub.ex
vendored
21
lib/web/cache/activity_pub.ex
vendored
@@ -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
|
||||
|
||||
18
lib/web/cache/cache.ex
vendored
18
lib/web/cache/cache.ex
vendored
@@ -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
|
||||
|
||||
@@ -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
60
lib/web/email/actor.ex
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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}" %>
|
||||
@@ -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) %>
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user