Merge remote-tracking branch 'origin/develop' into fix-attachment-dimensions

This commit is contained in:
Alex Gleason 2021-12-07 13:41:54 -05:00
commit 2c96668a2c
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
3836 changed files with 4191 additions and 12264 deletions

View file

@ -302,7 +302,7 @@ defmodule Pleroma.Activity do
|> Queries.by_object_id()
|> Queries.exclude_type("Delete")
|> select([u], u)
|> Repo.delete_all()
|> Repo.delete_all(timeout: :infinity)
|> elem(1)
|> Enum.find(fn
%{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id
@ -362,11 +362,9 @@ defmodule Pleroma.Activity do
end
def restrict_deactivated_users(query) do
deactivated_users =
from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|> Repo.all()
deactivated_users_query = from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
Activity.Queries.exclude_authors(query, deactivated_users)
from(activity in query, where: activity.actor not in subquery(deactivated_users_query))
end
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search

View file

@ -19,9 +19,7 @@ defmodule Pleroma.BBS.Handler do
def on_connect(username, ip, port, method) do
Logger.debug(fn ->
"""
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
inspect(port)
} using #{inspect(method)}
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{inspect(port)} using #{inspect(method)}
"""
end)
end

View file

@ -20,6 +20,140 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
]
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
|> Enum.any?(fn {_, v} -> Enum.any?(v, &is_binary/1) end)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the SimplePolicy configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf_simple,
media_removal: ["instance.tld"],
media_nsfw: ["instance.tld"],
federated_timeline_removal: ["instance.tld"],
report_removal: ["instance.tld"],
reject: ["instance.tld"],
followers_only: ["instance.tld"],
accept: ["instance.tld"],
avatar_removal: ["instance.tld"],
banner_removal: ["instance.tld"],
reject_deletes: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf_simple,
media_removal: [{"instance.tld", "Reason for media removal"}],
media_nsfw: [{"instance.tld", "Reason for media nsfw"}],
federated_timeline_removal: [{"instance.tld", "Reason for federated timeline removal"}],
report_removal: [{"instance.tld", "Reason for report removal"}],
reject: [{"instance.tld", "Reason for reject"}],
followers_only: [{"instance.tld", "Reason for followers only"}],
accept: [{"instance.tld", "Reason for accept"}],
avatar_removal: [{"instance.tld", "Reason for avatar removal"}],
banner_removal: [{"instance.tld", "Reason for banner removal"}],
reject_deletes: [{"instance.tld", "Reason for reject deletes"}]
```
""")
new_config =
Config.get([:mrf_simple])
|> Enum.map(fn {k, v} ->
{k,
Enum.map(v, fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)}
end)
Config.put([:mrf_simple], new_config)
:error
else
:ok
end
end
def check_quarantined_instances_tuples do
has_strings = Config.get([:instance, :quarantined_instances]) |> Enum.any?(&is_binary/1)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the quarantined_instances configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :instance,
quarantined_instances: ["instance.tld"]
```
Is now
```
config :pleroma, :instance,
quarantined_instances: [{"instance.tld", "Reason for quarantine"}]
```
""")
new_config =
Config.get([:instance, :quarantined_instances])
|> Enum.map(fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)
Config.put([:instance, :quarantined_instances], new_config)
:error
else
:ok
end
end
def check_transparency_exclusions_tuples do
has_strings = Config.get([:mrf, :transparency_exclusions]) |> Enum.any?(&is_binary/1)
if has_strings do
Logger.warn("""
!!!DEPRECATION WARNING!!!
Your config is using strings in the transparency_exclusions configuration instead of tuples. They should work for now, but you are advised to change to the new configuration to prevent possible issues later:
```
config :pleroma, :mrf,
transparency_exclusions: ["instance.tld"]
```
Is now
```
config :pleroma, :mrf,
transparency_exclusions: [{"instance.tld", "Reason to exlude transparency"}]
```
""")
new_config =
Config.get([:mrf, :transparency_exclusions])
|> Enum.map(fn
{instance, reason} -> {instance, reason}
instance -> {instance, ""}
end)
Config.put([:mrf, :transparency_exclusions], new_config)
:error
else
:ok
end
end
def check_hellthread_threshold do
if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
@ -34,20 +168,24 @@ defmodule Pleroma.Config.DeprecationWarnings do
end
def warn do
with :ok <- check_hellthread_threshold(),
:ok <- check_old_mrf_config(),
:ok <- check_media_proxy_whitelist_config(),
:ok <- check_welcome_message_config(),
:ok <- check_gun_pool_options(),
:ok <- check_activity_expiration_config(),
:ok <- check_remote_ip_plug_name(),
:ok <- check_uploders_s3_public_endpoint(),
:ok <- check_old_chat_shoutbox() do
:ok
else
_ ->
:error
end
[
check_hellthread_threshold(),
check_old_mrf_config(),
check_media_proxy_whitelist_config(),
check_welcome_message_config(),
check_gun_pool_options(),
check_activity_expiration_config(),
check_remote_ip_plug_name(),
check_uploders_s3_public_endpoint(),
check_old_chat_shoutbox(),
check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(),
check_simple_policy_tuples()
]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
_, _ -> :error
end)
end
def check_welcome_message_config do

View file

@ -21,9 +21,7 @@ defmodule Pleroma.Config.Oban do
"""
!!!OBAN CONFIG WARNING!!!
You are using old workers in Oban crontab settings, which were removed.
Please, remove setting from crontab in your config file (prod.secret.exs): #{
inspect(setting)
}
Please, remove setting from crontab in your config file (prod.secret.exs): #{inspect(setting)}
"""
|> Logger.warn()

View file

@ -148,9 +148,7 @@ defmodule Pleroma.Config.TransferTask do
rescue
error ->
error_msg =
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{
inspect(value)
} error: #{inspect(error)}"
"updating env causes error, group: #{inspect(group)}, key: #{inspect(key)}, value: #{inspect(value)} error: #{inspect(error)}"
Logger.warn(error_msg)

View file

@ -60,9 +60,7 @@ defmodule Pleroma.Emoji.Loader do
if not Enum.empty?(files) do
Logger.warn(
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{
Enum.join(files, ", ")
}"
"Found files in the emoji folder. These will be ignored, please move them to a subdirectory\nFound files: #{Enum.join(files, ", ")}"
)
end
@ -122,9 +120,7 @@ defmodule Pleroma.Emoji.Loader do
extensions = Config.get([:emoji, :pack_extensions])
Logger.info(
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{
Enum.join(extensions, ", ")
} files are emoji"
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
)
make_shortcode_to_file_map(pack_dir, extensions)

View file

@ -57,9 +57,7 @@ defmodule Pleroma.Gun.Conn do
else
error ->
Logger.warn(
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{
inspect(error)
}"
"Opening proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
)
error
@ -93,9 +91,7 @@ defmodule Pleroma.Gun.Conn do
else
error ->
Logger.warn(
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{
inspect(error)
}"
"Opening socks proxied connection to #{compose_uri_log(uri)} failed with error #{inspect(error)}"
)
error

View file

@ -8,6 +8,8 @@ defmodule Pleroma.Instances.Instance do
alias Pleroma.Instances
alias Pleroma.Instances.Instance
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Workers.BackgroundWorker
use Ecto.Schema
@ -195,4 +197,24 @@ defmodule Pleroma.Instances.Instance do
nil
end
end
@doc """
Deletes all users from an instance in a background task, thus also deleting
all of those users' activities and notifications.
"""
def delete_users_and_activities(host) when is_binary(host) do
BackgroundWorker.enqueue("delete_instance", %{"host" => host})
end
def perform(:delete_instance, host) when is_binary(host) do
User.Query.build(%{nickname: "@#{host}"})
|> Repo.chunk_stream(100, :batches)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
User.perform(:delete, user)
end)
end)
|> Stream.run()
end
end

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Maintenance do
def vacuum(args) do
case args do
"analyze" ->
Logger.info("Runnning VACUUM ANALYZE.")
Logger.info("Running VACUUM ANALYZE.")
Repo.query!(
"vacuum analyze;",
@ -18,7 +18,7 @@ defmodule Pleroma.Maintenance do
)
"full" ->
Logger.info("Runnning VACUUM FULL.")
Logger.info("Running VACUUM FULL.")
Logger.warn(
"Re-packing your entire database may take a while and will consume extra disk space during the process."

View file

@ -481,9 +481,7 @@ defmodule Pleroma.ModerationLog do
"visibility" => visibility
}
}) do
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{
visibility
}'"
"@#{actor_nickname} updated status ##{subject_id}, set sensitive: '#{sensitive}', visibility: '#{visibility}'"
end
def get_log_entry_message(%ModerationLog{
@ -523,9 +521,7 @@ defmodule Pleroma.ModerationLog do
"subject" => subjects
}
}) do
"@#{actor_nickname} re-sent confirmation email for users: #{
users_to_nicknames_string(subjects)
}"
"@#{actor_nickname} re-sent confirmation email for users: #{users_to_nicknames_string(subjects)}"
end
def get_log_entry_message(%ModerationLog{

View file

@ -72,6 +72,7 @@ defmodule Pleroma.Notification do
pleroma:emoji_reaction
pleroma:report
reblog
poll
}
def changeset(%Notification{} = notification, attrs) do
@ -127,6 +128,7 @@ defmodule Pleroma.Notification do
|> where([user_actor: user_actor], user_actor.is_active)
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|> exclude_blocked(user, exclude_blocked_opts)
|> exclude_blockers(user)
|> exclude_filtered(user)
|> exclude_visibility(opts)
end
@ -140,6 +142,17 @@ defmodule Pleroma.Notification do
|> FollowingRelationship.keep_following_or_not_domain_blocked(user)
end
defp exclude_blockers(query, user) do
if Pleroma.Config.get([:activitypub, :blockers_visible]) == true do
query
else
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
query
|> where([n, a], a.actor not in ^blocker_ap_ids)
end
end
defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
query
end
@ -379,7 +392,7 @@ defmodule Pleroma.Notification do
notifications =
Enum.map(potential_receivers, fn user ->
do_send = do_send && user in enabled_receivers
create_notification(activity, user, do_send)
create_notification(activity, user, do_send: do_send)
end)
|> Enum.reject(&is_nil/1)
@ -435,15 +448,18 @@ defmodule Pleroma.Notification do
end
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
unless skip?(activity, user) do
def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do
do_send = Keyword.get(opts, :do_send, true)
type = Keyword.get(opts, :type, type_from_activity(activity))
unless skip?(activity, user, opts) do
{:ok, %{notification: notification}} =
Multi.new()
|> Multi.insert(:notification, %Notification{
user_id: user.id,
activity: activity,
seen: mark_as_read?(activity, user),
type: type_from_activity(activity)
type: type
})
|> Marker.multi_set_last_read_id(user, "notifications")
|> Repo.transaction()
@ -457,6 +473,28 @@ defmodule Pleroma.Notification do
end
end
def create_poll_notifications(%Activity{} = activity) do
with %Object{data: %{"type" => "Question", "actor" => actor} = data} <-
Object.normalize(activity) do
voters =
case data do
%{"voters" => voters} when is_list(voters) -> voters
_ -> []
end
notifications =
Enum.reduce([actor | voters], [], fn ap_id, acc ->
with %User{local: true} = user <- User.get_by_ap_id(ap_id) do
[create_notification(activity, user, type: "poll") | acc]
else
_ -> acc
end
end)
{:ok, notifications}
end
end
@doc """
Returns a tuple with 2 elements:
{notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
@ -572,8 +610,10 @@ defmodule Pleroma.Notification do
Enum.uniq(ap_ids) -- thread_muter_ap_ids
end
@spec skip?(Activity.t(), User.t()) :: boolean()
def skip?(%Activity{} = activity, %User{} = user) do
def skip?(activity, user, opts \\ [])
@spec skip?(Activity.t(), User.t(), Keyword.t()) :: boolean()
def skip?(%Activity{} = activity, %User{} = user, opts) do
[
:self,
:invisible,
@ -581,17 +621,21 @@ defmodule Pleroma.Notification do
:recently_followed,
:filtered
]
|> Enum.find(&skip?(&1, activity, user))
|> Enum.find(&skip?(&1, activity, user, opts))
end
def skip?(_, _), do: false
def skip?(_activity, _user, _opts), do: false
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
def skip?(:self, %Activity{} = activity, %User{} = user) do
activity.data["actor"] == user.ap_id
@spec skip?(atom(), Activity.t(), User.t(), Keyword.t()) :: boolean()
def skip?(:self, %Activity{} = activity, %User{} = user, opts) do
cond do
opts[:type] == "poll" -> false
activity.data["actor"] == user.ap_id -> true
true -> false
end
end
def skip?(:invisible, %Activity{} = activity, _) do
def skip?(:invisible, %Activity{} = activity, _user, _opts) do
actor = activity.data["actor"]
user = User.get_cached_by_ap_id(actor)
User.invisible?(user)
@ -600,15 +644,27 @@ defmodule Pleroma.Notification do
def skip?(
:block_from_strangers,
%Activity{} = activity,
%User{notification_settings: %{block_from_strangers: true}} = user
%User{notification_settings: %{block_from_strangers: true}} = user,
opts
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
cond do
opts[:type] == "poll" -> false
user.ap_id == actor -> false
!User.following?(follower, user) -> true
true -> false
end
end
# To do: consider defining recency in hours and checking FollowingRelationship with a single SQL
def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity, %User{} = user) do
def skip?(
:recently_followed,
%Activity{data: %{"type" => "Follow"}} = activity,
%User{} = user,
_opts
) do
actor = activity.data["actor"]
Notification.for_user(user)
@ -618,9 +674,10 @@ defmodule Pleroma.Notification do
end)
end
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
def skip?(:filtered, %{data: %{"type" => type}}, _user, _opts) when type in ["Follow", "Move"],
do: false
def skip?(:filtered, activity, user) do
def skip?(:filtered, activity, user, _opts) do
object = Object.normalize(activity, fetch: false)
cond do
@ -638,7 +695,7 @@ defmodule Pleroma.Notification do
end
end
def skip?(_, _, _), do: false
def skip?(_type, _activity, _user, _opts), do: false
def mark_as_read?(activity, target_user) do
user = Activity.user_actor(activity)

View file

@ -29,9 +29,7 @@ defmodule Pleroma.Telemetry.Logger do
_
) do
Logger.debug(fn ->
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{
reclaim_max
} connections"
"Connection pool is exhausted (reached #{max_connections} connections). Starting idle connection cleanup to reclaim as much as #{reclaim_max} connections"
end)
end
@ -73,9 +71,7 @@ defmodule Pleroma.Telemetry.Logger do
_
) do
Logger.warn(fn ->
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{
inspect(reason)
}"
"Pool worker for #{key}: Client #{inspect(client_pid)} died before releasing the connection with #{inspect(reason)}"
end)
end

View file

@ -124,7 +124,6 @@ defmodule Pleroma.User do
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
field(:mastofe_settings, :map, default: nil)
field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false)
@ -1713,7 +1712,6 @@ defmodule Pleroma.User do
ap_enabled: false,
is_moderator: false,
is_admin: false,
mastofe_settings: nil,
mascot: nil,
emoji: %{},
pleroma_settings_store: %{},
@ -2248,7 +2246,7 @@ defmodule Pleroma.User do
def change_email(user, email) do
user
|> cast(%{email: email}, [:email])
|> validate_required([:email])
|> maybe_validate_required_email(false)
|> unique_constraint(:email)
|> validate_format(:email, @email_regex)
|> update_and_set_cache()
@ -2331,13 +2329,6 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
def mastodon_settings_update(user, settings) do
user
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|> validate_required([:mastofe_settings])
|> update_and_set_cache()
end
@spec confirmation_changeset(User.t(), keyword()) :: Changeset.t()
def confirmation_changeset(user, set_confirmation: confirmed?) do
params =
@ -2483,8 +2474,8 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
def active_user_count(weeks \\ 4) do
active_after = Timex.shift(NaiveDateTime.utc_now(), weeks: -weeks)
def active_user_count(days \\ 30) do
active_after = Timex.shift(NaiveDateTime.utc_now(), days: -days)
__MODULE__
|> where([u], u.last_active_at >= ^active_after)

View file

@ -25,6 +25,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker
alias Pleroma.Workers.PollWorker
import Ecto.Query
import Pleroma.Web.ActivityPub.Utils
@ -288,6 +289,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
_ <- notify_and_stream(activity),
:ok <- maybe_schedule_poll_notifications(activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
@ -302,6 +304,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp maybe_schedule_poll_notifications(activity) do
PollWorker.schedule_poll_end(activity)
:ok
end
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
def listen(%{to: to, actor: actor, context: context, object: object} = params) do
additional = params[:additional] || %{}
@ -434,6 +441,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts)
|> restrict_blockers_visibility(opts)
|> restrict_recipients(recipients, opts[:user])
|> restrict_filtered(opts)
|> where(
@ -1021,7 +1029,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(
[activity, object: o] in query,
# You don't block the author
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
# You don't block any recipients, and didn't author the post
where:
fragment(
"((not (? && ?)) or ? = ?)",
@ -1030,12 +1041,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.actor,
^user.ap_id
),
# You don't block the domain of any recipients, and didn't author the post
where:
fragment(
"recipients_contain_blocked_domains(?, ?) = false",
"(recipients_contain_blocked_domains(?, ?) = false) or ? = ?",
activity.recipients,
^domain_blocks
^domain_blocks,
activity.actor,
^user.ap_id
),
# It's not a boost of a user you block
where:
fragment(
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
@ -1043,6 +1060,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.data,
^blocked_ap_ids
),
# You don't block the author's domain, and also don't follow the author
where:
fragment(
"(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)",
@ -1051,6 +1070,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
activity.actor,
^following_ap_ids
),
# Same as above, but checks the Object
where:
fragment(
"(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)",
@ -1064,6 +1085,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_blocked(query, _), do: query
defp restrict_blockers_visibility(query, %{blocking_user: %User{} = user}) do
if Config.get([:activitypub, :blockers_visible]) == true do
query
else
blocker_ap_ids = User.incoming_relationships_ungrouped_ap_ids(user, [:block])
from(
activity in query,
# The author doesn't block you
where: fragment("not (? = ANY(?))", activity.actor, ^blocker_ap_ids),
# It's not a boost of a user that blocks you
where:
fragment(
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
activity.data,
activity.data,
^blocker_ap_ids
)
)
end
end
defp restrict_blockers_visibility(query, _), do: query
defp restrict_unlisted(query, %{restrict_unlisted: true}) do
from(
activity in query,
@ -1290,6 +1336,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_state(opts)
|> restrict_favorited_by(opts)
|> restrict_blocked(restrict_blocked_opts)
|> restrict_blockers_visibility(opts)
|> restrict_muted(restrict_muted_opts)
|> restrict_filtered(opts)
|> restrict_media(opts)
@ -1590,9 +1637,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%User{} = old_user <- User.get_by_nickname(nickname),
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
Logger.info(
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
data[:ap_id]
}, renaming."
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{data[:ap_id]}, renaming."
)
old_user

View file

@ -283,15 +283,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do
conn
|> put_status(:bad_request)
|> json("Invalid HTTP Signature")
end
# POST /relay/inbox -or- POST /internal/fetch/inbox
def inbox(conn, params) do
if params["type"] == "Create" && FederatingPlug.federating?() do
def inbox(conn, %{"type" => "Create"} = params) do
if FederatingPlug.federating?() do
post_inbox_relayed_create(conn, params)
else
post_inbox_fallback(conn, params)
conn
|> put_status(:bad_request)
|> json("Not federating")
end
end
def inbox(conn, _params) do
conn
|> put_status(:bad_request)
|> json("error, missing HTTP Signature")
end
defp post_inbox_relayed_create(conn, params) do
Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source"
@ -302,23 +316,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
json(conn, "ok")
end
defp post_inbox_fallback(conn, params) do
headers = Enum.into(conn.req_headers, %{})
if headers["signature"] && params["actor"] &&
String.contains?(headers["signature"], params["actor"]) do
Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
)
Logger.debug(inspect(conn.req_headers))
end
conn
|> put_status(:bad_request)
|> json(dgettext("errors", "error"))
end
defp represent_service_actor(%User{} = user, conn) do
with {:ok, user} <- User.ensure_keys_present(user) do
conn

View file

@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft
require Pleroma.Constants
@ -125,6 +126,37 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|> Pleroma.Maps.put_if_present("context", context), []}
end
@spec note(ActivityDraft.t()) :: {:ok, map(), keyword()}
def note(%ActivityDraft{} = draft) do
data =
%{
"type" => "Note",
"to" => draft.to,
"cc" => draft.cc,
"content" => draft.content_html,
"summary" => draft.summary,
"sensitive" => draft.sensitive,
"context" => draft.context,
"attachment" => draft.attachments,
"actor" => draft.user.ap_id,
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
}
|> add_in_reply_to(draft.in_reply_to)
|> Map.merge(draft.extra)
{:ok, data, []}
end
defp add_in_reply_to(object, nil), do: object
defp add_in_reply_to(object, in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
end
end
def chat_message(actor, recipient, content, opts \\ []) do
basic = %{
"id" => Utils.generate_object_id(),

View file

@ -33,9 +33,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do
%{
key: :transparency_exclusions,
label: "MRF transparency exclusions",
type: {:list, :string},
type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
description:
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed.",
suggestions: [
"exclusion.com"
]
@ -100,6 +102,11 @@ defmodule Pleroma.Web.ActivityPub.MRF do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
@spec instance_list_from_tuples([{String.t(), String.t()}]) :: [String.t()]
def instance_list_from_tuples(list) do
Enum.map(list, fn {instance, _} -> instance end)
end
def describe(policies) do
{:ok, policy_configs} =
policies
@ -150,9 +157,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
[description | acc]
else
Logger.warn(
"#{policy} config description doesn't have one or all required keys #{
inspect(@required_description_keys)
}"
"#{policy} config description doesn't have one or all required keys #{inspect(@required_description_keys)}"
)
acc

View file

@ -159,6 +159,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
%{
key: :replace,
type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
description: """
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.

View file

@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true
def describe,
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Map.new()}}
@impl true
def config_description do

View file

@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
Config.get([:mrf_simple, :accept])
instance_list(:accept)
|> MRF.subdomains_regex()
cond do
@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_reject(%{host: actor_host} = _actor_info, object) do
rejects =
Config.get([:mrf_simple, :reject])
instance_list(:reject)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
@ -44,7 +44,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
)
when length(child_attachment) > 0 do
media_removal =
Config.get([:mrf_simple, :media_removal])
instance_list(:media_removal)
|> MRF.subdomains_regex()
object =
@ -68,7 +68,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
} = object
) do
media_nsfw =
Config.get([:mrf_simple, :media_nsfw])
instance_list(:media_nsfw)
|> MRF.subdomains_regex()
object =
@ -85,7 +85,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
Config.get([:mrf_simple, :federated_timeline_removal])
instance_list(:federated_timeline_removal)
|> MRF.subdomains_regex()
object =
@ -112,7 +112,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_followers_only(%{host: actor_host} = _actor_info, object) do
followers_only =
Config.get([:mrf_simple, :followers_only])
instance_list(:followers_only)
|> MRF.subdomains_regex()
object =
@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
report_removal =
Config.get([:mrf_simple, :report_removal])
instance_list(:report_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
@ -151,7 +151,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
avatar_removal =
Config.get([:mrf_simple, :avatar_removal])
instance_list(:avatar_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
@ -165,7 +165,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
banner_removal =
Config.get([:mrf_simple, :banner_removal])
instance_list(:banner_removal)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@ -185,12 +185,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_object(object), do: {:ok, object}
defp instance_list(config_key) do
Config.get([:mrf_simple, config_key])
|> MRF.instance_list_from_tuples()
end
@impl true
def filter(%{"type" => "Delete", "actor" => actor} = object) do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
Config.get([:mrf_simple, :reject_deletes])
instance_list(:reject_deletes)
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do
@ -253,14 +258,42 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@impl true
def describe do
exclusions = Config.get([:mrf, :transparency_exclusions])
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
mrf_simple_excluded =
Config.get(:mrf_simple)
|> Enum.map(fn {rule, instances} ->
{rule, Enum.reject(instances, fn {host, _} -> host in exclusions end)}
end)
mrf_simple =
Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
mrf_simple_excluded
|> Enum.map(fn {rule, instances} ->
{rule, Enum.map(instances, fn {host, _} -> host end)}
end)
|> Map.new()
{:ok, %{mrf_simple: mrf_simple}}
# This is for backwards compatibility. We originally didn't sent
# extra info like a reason why an instance was rejected/quarantined/etc.
# Because we didn't want to break backwards compatibility it was decided
# to add an extra "info" key.
mrf_simple_info =
mrf_simple_excluded
|> Enum.map(fn {rule, instances} ->
{rule, Enum.reject(instances, fn {_, reason} -> reason == "" end)}
end)
|> Enum.reject(fn {_, instances} -> instances == [] end)
|> Enum.map(fn {rule, instances} ->
instances =
instances
|> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end)
|> Map.new()
{rule, instances}
end)
|> Map.new()
{:ok, %{mrf_simple: mrf_simple, mrf_simple_info: mrf_simple_info}}
end
@impl true
@ -270,70 +303,67 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
label: "MRF Simple",
description: "Simple ingress policies",
children: [
%{
key: :media_removal,
type: {:list, :string},
description: "List of instances to strip media attachments from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :media_nsfw,
label: "Media NSFW",
type: {:list, :string},
description: "List of instances to tag all media as NSFW (sensitive) from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :federated_timeline_removal,
type: {:list, :string},
description:
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :reject,
type: {:list, :string},
description: "List of instances to reject activities from (except deletes)",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :accept,
type: {:list, :string},
description: "List of instances to only accept activities from (except deletes)",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :followers_only,
type: {:list, :string},
description: "Force posts from the given instances to be visible by followers only",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :report_removal,
type: {:list, :string},
description: "List of instances to reject reports from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :avatar_removal,
type: {:list, :string},
description: "List of instances to strip avatars from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :banner_removal,
type: {:list, :string},
description: "List of instances to strip banners from",
suggestions: ["example.com", "*.example.com"]
},
%{
key: :reject_deletes,
type: {:list, :string},
description: "List of instances to reject deletions from",
suggestions: ["example.com", "*.example.com"]
}
]
children:
[
%{
key: :media_removal,
description:
"List of instances to strip media attachments from and the reason for doing so"
},
%{
key: :media_nsfw,
label: "Media NSFW",
description:
"List of instances to tag all media as NSFW (sensitive) from and the reason for doing so"
},
%{
key: :federated_timeline_removal,
description:
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline and the reason for doing so"
},
%{
key: :reject,
description:
"List of instances to reject activities from (except deletes) and the reason for doing so"
},
%{
key: :accept,
description:
"List of instances to only accept activities from (except deletes) and the reason for doing so"
},
%{
key: :followers_only,
description:
"Force posts from the given instances to be visible by followers only and the reason for doing so"
},
%{
key: :report_removal,
description: "List of instances to reject reports from and the reason for doing so"
},
%{
key: :avatar_removal,
description: "List of instances to strip avatars from and the reason for doing so"
},
%{
key: :banner_removal,
description: "List of instances to strip banners from and the reason for doing so"
},
%{
key: :reject_deletes,
description: "List of instances to reject deletions from and the reason for doing so"
}
]
|> Enum.map(fn setting ->
Map.merge(
setting,
%{
type: {:list, :tuple},
key_placeholder: "instance",
value_placeholder: "reason",
suggestions: [{"example.com", "Some reason"}, {"*.example.com", "Another reason"}]
}
)
end)
}
end
end

View file

@ -38,9 +38,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do
end
else
Logger.debug(
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{
size_limit
} B)"
"MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)"
)
nil

View file

@ -23,9 +23,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
def filter(%{"actor" => actor} = message) do
with {:ok, match, subchain} <- lookup_subchain(actor) do
Logger.debug(
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{
inspect(subchain)
}"
"[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}"
)
MRF.filter(subchain, message)

View file

@ -37,7 +37,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
def describe do
mrf_user_allowlist =
Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
|> Map.new(fn {k, v} -> {k, length(v)} end)
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
end

View file

@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@impl true
def describe,
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Map.new()}}
@impl true
def config_description do

View file

@ -213,6 +213,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def stringify_keys(object) when is_map(object) do
object
|> Enum.filter(fn {_, v} -> v != nil end)
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
use Ecto.Schema
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@ -14,12 +13,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
end
def cast_data(data) do

View file

@ -10,19 +10,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
require Pleroma.Constants
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.User
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:target)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
field(:type)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
end
def cast_and_validate(data) do

View file

@ -20,13 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
field(:context, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:published, ObjectValidators.DateTime)
end

View file

@ -15,12 +15,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
@derive Jason.Encoder
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
field(:type, :string)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
end
end
field(:name, :string)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)

View file

@ -6,10 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -18,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
@derive Jason.Encoder
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
object_fields()
status_object_fields()
end
end
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
end

View file

@ -5,11 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -18,38 +15,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
@derive Jason.Encoder
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
object_fields()
status_object_fields()
end
end
end
def cast_and_apply(data) do

View file

@ -5,20 +5,21 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
@derive Jason.Encoder
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
end
def cast_data(data) do
@ -30,8 +31,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Block"])
|> validate_actor_presence()
|> validate_actor_presence(field_name: :object)
|> CommonValidations.validate_actor_presence()
|> CommonValidations.validate_actor_presence(field_name: :object)
end
def cast_and_validate(data) do

View file

@ -0,0 +1,68 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFields do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
# Activities and Objects, except (Create)ChatMessage
defmacro message_fields do
quote bind_quoted: binding() do
field(:type, :string)
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
end
end
defmacro activity_fields do
quote bind_quoted: binding() do
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
end
end
# All objects except Answer and CHatMessage
defmacro object_fields do
quote bind_quoted: binding() do
field(:content, :string)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
embeds_many(:attachment, AttachmentValidator)
end
end
# Basically objects that aren't ChatMessage and Answer
defmacro status_object_fields do
quote bind_quoted: binding() do
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
embeds_many(:tag, TagValidator)
field(:name, :string)
field(:summary, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
field(:sensitive, :boolean, default: false)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end
end
end

View file

@ -17,11 +17,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator do
@primary_key false
embedded_schema do
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
activity_fields()
end
end
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
end
def cast_and_apply(data) do

View file

@ -20,14 +20,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
field(:expires_at, ObjectValidators.DateTime)
# Should be moved to object, done for CommonAPI.Utils.make_context

View file

@ -15,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
field(:deleted_activity_id, ObjectValidators.ObjectID)
field(:object, ObjectValidators.ObjectID)
end
def cast_data(data) do

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
@ -15,14 +14,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
field(:context, :string)
field(:content, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do

View file

@ -5,11 +5,8 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -19,38 +16,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
# Extends from NoteValidator
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:name, :string)
field(:summary, :string)
field(:content, :string)
field(:context, :string)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
object_fields()
status_object_fields()
end
end
end
def cast_and_apply(data) do

View file

@ -5,20 +5,20 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
field(:state, :string, default: "pending")
end

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.Utils
@ -16,13 +15,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
field(:context, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
end
def cast_and_validate(data) do

View file

@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.TagValidator
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@ -20,35 +18,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
# Extends from NoteValidator
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
embeds_many(:tag, TagValidator)
field(:type, :string)
field(:content, :string)
field(:context, :string)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
field(:summary, :string)
field(:published, ObjectValidators.DateTime)
field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
object_fields()
status_object_fields()
end
end
field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
use Ecto.Schema
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.User
import Ecto.Changeset
@ -15,12 +14,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:object, ObjectValidators.ObjectID)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
activity_fields()
end
end
end
def cast_and_validate(data) do

View file

@ -13,11 +13,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
quote do
unquote do
import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
message_fields()
end
end
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
# In this case, we save the full object in this activity instead of just a
# reference, so we can always see what was actually changed by this.
field(:object, :map)

View file

@ -63,18 +63,17 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
date: date
})
with {:ok, %{status: code}} when code in 200..299 <-
result =
HTTP.post(
inbox,
json,
[
{"Content-Type", "application/activity+json"},
{"Date", date},
{"signature", signature},
{"digest", digest}
]
) do
with {:ok, %{status: code}} = result when code in 200..299 <-
HTTP.post(
inbox,
json,
[
{"Content-Type", "application/activity+json"},
{"Date", date},
{"signature", signature},
{"digest", digest}
]
) do
if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do
Instances.set_reachable(inbox)
end
@ -112,6 +111,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)

View file

@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
alias Pleroma.Workers.PollWorker
require Logger
@ -194,7 +195,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
# - Set up notifications
@impl true
def handle(%{data: %{"type" => "Create"}} = activity, meta) do
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], meta),
with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{:ok, notifications} = Notification.create_notifications(activity, do_send: false)
{:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)
@ -388,7 +389,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
{:ok, object, meta}
end
def handle_object_creation(%{"type" => "ChatMessage"} = object, meta) do
def handle_object_creation(%{"type" => "ChatMessage"} = object, _activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
actor = User.get_cached_by_ap_id(object.data["actor"])
recipient = User.get_cached_by_ap_id(hd(object.data["to"]))
@ -423,7 +424,14 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
def handle_object_creation(%{"type" => "Question"} = object, activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
PollWorker.schedule_poll_end(activity)
{:ok, object, meta}
end
end
def handle_object_creation(%{"type" => "Answer"} = object_map, _activity, meta) do
with {:ok, object, meta} <- Pipeline.common_pipeline(object_map, meta) do
Object.increase_vote_count(
object.data["inReplyTo"],
@ -435,15 +443,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
end
def handle_object_creation(%{"type" => objtype} = object, meta)
when objtype in ~w[Audio Video Question Event Article Note Page] do
def handle_object_creation(%{"type" => objtype} = object, _activity, meta)
when objtype in ~w[Audio Video Event Article Note Page] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta}
end
end
# Nothing to do
def handle_object_creation(object, meta) do
def handle_object_creation(object, _activity, meta) do
{:ok, object, meta}
end

View file

@ -49,7 +49,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:statuses"]}
when action in [:list_user_statuses, :list_instance_statuses]
when action in [:list_user_statuses]
)
plug(
@ -81,24 +81,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
action_fallback(AdminAPI.FallbackController)
def list_instance_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
result =
ActivityPub.fetch_statuses(nil, %{
instance: instance,
limit: page_size,
offset: (page - 1) * page_size,
exclude_reblogs: not with_reblogs,
total: true
})
conn
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
end
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
godmode = params["godmode"] == "true" || params["godmode"] == true

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InstanceController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 3]
alias Pleroma.Instances.Instance
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
@default_page_size 50
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:statuses"]}
when action in [:list_statuses]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write:accounts", "admin:write:statuses"]}
when action in [:delete]
)
action_fallback(AdminAPI.FallbackController)
def list_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params)
result =
ActivityPub.fetch_statuses(nil, %{
instance: instance,
limit: page_size,
offset: (page - 1) * page_size,
exclude_reblogs: not with_reblogs,
total: true
})
conn
|> put_view(AdminAPI.StatusView)
|> render("index.json", %{total: result[:total], activities: result[:items], as: :activity})
end
def delete(conn, %{"instance" => instance}) do
with {:ok, _job} <- Instance.delete_users_and_activities(instance) do
json(conn, instance)
end
end
defp page_params(params) do
{
fetch_integer_param(params, "page", 1),
fetch_integer_param(params, "page_size", @default_page_size)
}
end
end

View file

@ -195,7 +195,8 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
"pleroma:chat_mention",
"pleroma:report",
"move",
"follow_request"
"follow_request",
"poll"
],
description: """
The type of event that resulted in the notification.

View file

@ -121,7 +121,10 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
type: :object,
required: [:email, :password],
properties: %{
email: %Schema{type: :string, description: "New email"},
email: %Schema{
type: :string,
description: "New email. Set to blank to remove the user's email."
},
password: %Schema{type: :string, description: "Current password"}
}
}

View file

@ -487,9 +487,7 @@ defmodule Pleroma.Web.CommonAPI do
else
{what, result} = error ->
Logger.warn(
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
activity_id
}"
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{activity_id}"
)
{:error, error}

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
@ -213,8 +214,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
emoji = Map.merge(emoji, summary_emoji)
{:ok, note_data, _meta} = Builder.note(draft)
object =
Utils.make_note_data(draft)
note_data
|> Map.put("emoji", emoji)
|> Map.put("source", draft.status)
|> Map.put("generator", draft.params[:generator])

View file

@ -291,33 +291,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.html_escape("text/html")
end
def make_note_data(%ActivityDraft{} = draft) do
%{
"type" => "Note",
"to" => draft.to,
"cc" => draft.cc,
"content" => draft.content_html,
"summary" => draft.summary,
"sensitive" => draft.sensitive,
"context" => draft.context,
"attachment" => draft.attachments,
"actor" => draft.user.ap_id,
"tag" => Keyword.values(draft.tags) |> Enum.uniq()
}
|> add_in_reply_to(draft.in_reply_to)
|> Map.merge(draft.extra)
end
defp add_in_reply_to(object, nil), do: object
defp add_in_reply_to(object, in_reply_to) do
with %Object{} = in_reply_to_object <- Object.normalize(in_reply_to, fetch: false) do
Map.put(object, "inReplyTo", in_reply_to_object.data["id"])
else
_ -> object
end
end
def format_naive_asctime(date) do
date |> DateTime.from_naive!("Etc/UTC") |> format_asctime
end

View file

@ -18,6 +18,8 @@ defmodule Pleroma.Web.Feed.UserController do
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
else
_ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
end
end

View file

@ -1,61 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AuthController
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
# Note: :index action handles attempt of unauthenticated access to private instance with redirect
plug(:skip_public_check when action == :index)
plug(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated}
when action == :index
)
plug(:skip_auth when action == :manifest)
@doc "GET /web/*path"
def index(conn, _params) do
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
conn
|> put_layout(false)
|> render("index.html",
token: token.token,
user: user,
custom_emojis: Pleroma.Emoji.get_all()
)
else
_ ->
conn
|> put_session(:return_to, conn.request_path)
|> redirect(to: "/web/login")
end
end
@doc "GET /web/manifest.json"
def manifest(conn, _params) do
render(conn, "manifest.json")
end
@doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere"
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
with {:ok, _} <- User.mastodon_settings_update(user, settings) do
json(conn, %{})
else
e ->
conn
|> put_status(:internal_server_error)
|> json(%{error: inspect(e)})
end
end
end

View file

@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@local_mastodon_name "Mastodon-Local"
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
@doc "POST /api/v1/apps"
@ -35,7 +33,6 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|> Map.put(:scopes, scopes)
with cs <- App.register_changeset(%App{}, app_attrs),
false <- cs.changes[:client_name] == @local_mastodon_name,
{:ok, app} <- Repo.insert(cs) do
render(conn, "show.json", app: app)
end

View file

@ -7,77 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Helpers.AuthHelper
alias Pleroma.Helpers.UriHelper
alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
alias Pleroma.Web.TwitterAPI.TwitterAPI
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
@local_mastodon_name "Mastodon-Local"
@doc "GET /web/login"
# Local Mastodon FE login callback action
def login(conn, %{"code" => auth_token} = params) do
with {:ok, app} <- local_mastofe_app(),
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
{:ok, oauth_token} <- Token.exchange_token(app, auth) do
redirect_to =
conn
|> local_mastodon_post_login_path()
|> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token})
conn
|> AuthHelper.put_session_token(oauth_token.token)
|> redirect(to: redirect_to)
else
_ -> redirect_to_oauth_form(conn, params)
end
end
def login(conn, params) do
with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn,
{:ok, %{id: ^app_id}} <- local_mastofe_app() do
redirect(conn, to: local_mastodon_post_login_path(conn))
else
_ -> redirect_to_oauth_form(conn, params)
end
end
defp redirect_to_oauth_form(conn, _params) do
with {:ok, app} <- local_mastofe_app() do
path =
Routes.o_auth_path(conn, :authorize,
response_type: "code",
client_id: app.client_id,
redirect_uri: ".",
scope: Enum.join(app.scopes, " ")
)
redirect(conn, to: path)
end
end
@doc "DELETE /auth/sign_out"
def logout(conn, _) do
conn =
with %{assigns: %{token: %Token{} = oauth_token}} <- conn,
session_token = AuthHelper.get_session_token(conn),
{:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do
AuthHelper.delete_session_token(conn)
else
_ -> conn
end
redirect(conn, to: "/")
end
@doc "POST /auth/password"
def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
@ -86,23 +21,4 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
json_response(conn, :no_content, "")
end
defp local_mastodon_post_login_path(conn) do
case get_session(conn, :return_to) do
nil ->
Routes.masto_fe_path(conn, :index, ["getting-started"])
return_to ->
delete_session(conn, :return_to)
return_to
end
end
@spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
def local_mastofe_app do
App.get_or_make(
%{client_name: @local_mastodon_name, redirect_uris: "."},
["read", "write", "follow", "push", "admin"]
)
end
end

View file

@ -50,6 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
favourite
move
pleroma:emoji_reaction
poll
}
def index(%{assigns: %{user: user}} = conn, params) do
params =

View file

@ -95,7 +95,20 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
{:ok, data} = MRF.describe()
data
|> Map.merge(%{quarantined_instances: quarantined})
|> Map.put(
:quarantined_instances,
Enum.map(quarantined, fn {instance, _reason} -> instance end)
)
# This is for backwards compatibility. We originally didn't sent
# extra info like a reason why an instance was rejected/quarantined/etc.
# Because we didn't want to break backwards compatibility it was decided
# to add an extra "info" key.
|> Map.put(:quarantined_instances_info, %{
"quarantined_instances" =>
quarantined
|> Enum.map(fn {instance, reason} -> {instance, %{"reason" => reason}} end)
|> Map.new()
})
else
%{}
end

View file

@ -112,6 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
"move" ->
put_target(response, activity, reading_user, %{})
"poll" ->
put_status(response, activity, reading_user, status_render_opts)
"pleroma:emoji_reaction" ->
response
|> put_status(parent_activity_fn.(), reading_user, status_render_opts)

View file

@ -49,9 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
def websocket_init(state) do
Logger.debug(
"#{__MODULE__} accepted websocket connection for user #{
(state.user || %{id: "anonymous"}).id
}, topic #{state.topic}"
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
)
Streamer.add_socket(state.topic, state.user)
@ -106,9 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
def terminate(reason, _req, state) do
Logger.debug(
"#{__MODULE__} terminating websocket connection for user #{
(state.user || %{id: "anonymous"}).id
}, topic #{state.topic || "?"}: #{inspect(reason)}"
"#{__MODULE__} terminating websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic || "?"}: #{inspect(reason)}"
)
Streamer.remove_socket(state.topic)

View file

@ -35,7 +35,9 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
openRegistrations: Config.get([:instance, :registrations_open]),
usage: %{
users: %{
total: Map.get(stats, :user_count, 0)
total: Map.get(stats, :user_count, 0),
activeMonth: Pleroma.User.active_user_count(30),
activeHalfyear: Pleroma.User.active_user_count(180)
},
localPosts: Map.get(stats, :status_count, 0)
},

View file

@ -597,9 +597,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
# Special case: Local MastodonFE
defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login)
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)

View file

@ -0,0 +1,23 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.UserIsStaffPlug do
import Pleroma.Web.TranslationHelpers
import Plug.Conn
alias Pleroma.User
def init(options) do
options
end
def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _), do: conn
def call(%{assigns: %{user: %User{is_moderator: true}}} = conn, _), do: conn
def call(conn, _) do
conn
|> render_error(:forbidden, "User is not a staff member.")
|> halt()
end
end

View file

@ -4,7 +4,6 @@
defmodule Pleroma.Web.Preload do
alias Phoenix.HTML
require Logger
def build_tags(_conn, params) do
preload_data =

View file

@ -124,8 +124,8 @@ defmodule Pleroma.Web.Push.Impl do
def format_body(activity, actor, object, mastodon_type \\ nil)
def format_body(_activity, actor, %{data: %{"type" => "ChatMessage", "content" => content}}, _) do
case content do
def format_body(_activity, actor, %{data: %{"type" => "ChatMessage"} = data}, _) do
case data["content"] do
nil -> "@#{actor.nickname}: (Attachment)"
content -> "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
end

View file

@ -26,7 +26,7 @@ defmodule Pleroma.Web.Push.Subscription do
end
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
@supported_alert_types ~w[follow favourite mention reblog pleroma:chat_mention pleroma:emoji_reaction]a
@supported_alert_types ~w[follow favourite mention reblog poll pleroma:chat_mention pleroma:emoji_reaction]a
defp alerts(%{data: %{alerts: alerts}}) do
alerts = Map.take(alerts, @supported_alert_types)

View file

@ -96,14 +96,12 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth)
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Web.Plugs.UserIsAdminPlug)
plug(Pleroma.Web.Plugs.UserIsStaffPlug)
plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :mastodon_html do
plug(:browser)
plug(:authenticate)
plug(:after_auth)
pipeline :require_admin do
plug(Pleroma.Web.Plugs.UserIsAdminPlug)
end
pipeline :pleroma_html do
@ -160,7 +158,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through(:admin_api)
pipe_through([:admin_api, :require_admin])
put("/users/disable_mfa", AdminAPIController, :disable_mfa)
put("/users/tag", AdminAPIController, :tag_users)
@ -213,7 +211,8 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
get("/instances/:instance/statuses", InstanceController, :list_statuses)
delete("/instances/:instance", InstanceController, :delete)
get("/instance_document/:name", InstanceDocumentController, :show)
patch("/instance_document/:name", InstanceDocumentController, :update)
@ -265,7 +264,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/pack" do
pipe_through(:admin_api)
pipe_through([:admin_api, :require_admin])
post("/", EmojiPackController, :create)
patch("/", EmojiPackController, :update)
@ -280,7 +279,7 @@ defmodule Pleroma.Web.Router do
# Modifying packs
scope "/packs" do
pipe_through(:admin_api)
pipe_through([:admin_api, :require_admin])
get("/import", EmojiPackController, :import_from_filesystem)
get("/remote", EmojiPackController, :remote)
@ -542,13 +541,6 @@ defmodule Pleroma.Web.Router do
get("/timelines/list/:list_id", TimelineController, :list)
end
scope "/api/web", Pleroma.Web do
pipe_through(:authenticated_api)
# Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere
put("/settings", MastoFEController, :put_settings)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:app_api)
@ -745,22 +737,9 @@ defmodule Pleroma.Web.Router do
end
scope "/", Pleroma.Web do
pipe_through(:api)
pipe_through(:pleroma_html)
get("/web/manifest.json", MastoFEController, :manifest)
end
scope "/", Pleroma.Web do
pipe_through(:mastodon_html)
get("/web/login", MastodonAPI.AuthController, :login)
delete("/auth/sign_out", MastodonAPI.AuthController, :logout)
post("/auth/password", MastodonAPI.AuthController, :password_reset)
get("/web/*path", MastoFEController, :index)
get("/embed/:id", EmbedController, :show)
post("/auth/password", TwitterAPI.PasswordController, :request)
end
scope "/proxy/", Pleroma.Web do

View file

@ -1,35 +0,0 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta content='width=device-width, initial-scale=1' name='viewport'>
<title>
<%= Config.get([:instance, :name]) %>
</title>
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
<script crossorigin='anonymous' src="/packs/locales.js"></script>
<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/home_timeline.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/notifications.js'>
<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
<script src="/packs/core/common.js"></script>
<link rel="stylesheet" media="all" href="/packs/core/common.css" />
<script src="/packs/flavours/glitch/common.js"></script>
<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" />
<script src="/packs/flavours/glitch/home.js"></script>
</head>
<body class='app-body no-reduce-motion system-font'>
<div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
</div>
</body>
</html>

View file

@ -11,9 +11,23 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
require Logger
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request)
@doc "POST /auth/password"
def request(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
TwitterAPI.password_reset(nickname_or_email)
json_response(conn, :no_content, "")
end
def reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),

View file

@ -1,91 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastoFEView do
use Pleroma.Web, :view
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.CustomEmojiView
def initial_state(token, user, custom_emojis) do
limit = Config.get([:instance, :limit])
%{
meta: %{
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
access_token: token,
locale: "en",
domain: Pleroma.Web.Endpoint.host(),
admin: "1",
me: "#{user.id}",
unfollow_modal: false,
boost_modal: false,
delete_modal: true,
auto_play_gif: false,
display_sensitive_media: false,
reduce_motion: false,
max_toot_chars: limit,
mascot: User.get_mascot(user)["url"]
},
poll_limits: Config.get([:instance, :poll_limits]),
rights: %{
delete_others_notice: present?(user.is_moderator),
admin: present?(user.is_admin)
},
compose: %{
me: "#{user.id}",
default_privacy: user.default_scope,
default_sensitive: false,
allow_content_types: Config.get([:instance, :allowed_post_formats])
},
media_attachments: %{
accept_content_types: [
".jpg",
".jpeg",
".png",
".gif",
".webm",
".mp4",
".m4v",
"image\/jpeg",
"image\/png",
"image\/gif",
"video\/webm",
"video\/mp4"
]
},
settings: user.mastofe_settings || %{},
push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
char_limit: limit
}
|> Jason.encode!()
|> Phoenix.HTML.raw()
end
defp present?(nil), do: false
defp present?(false), do: false
defp present?(_), do: true
def render("manifest.json", _params) do
%{
name: Config.get([:instance, :name]),
description: Config.get([:instance, :description]),
icons: Config.get([:manifest, :icons]),
theme_color: Config.get([:manifest, :theme_color]),
background_color: Config.get([:manifest, :background_color]),
display: "standalone",
scope: Pleroma.Web.Endpoint.url(),
start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]),
categories: [
"social"
],
serviceworker: %{
src: "/sw.js"
}
}
end
end

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.BackgroundWorker do
alias Pleroma.Instances.Instance
alias Pleroma.User
use Pleroma.Workers.WorkerHelper, queue: "background"
@ -38,4 +39,8 @@ defmodule Pleroma.Workers.BackgroundWorker do
Pleroma.FollowingRelationship.move_following(origin, target)
end
def perform(%Job{args: %{"op" => "delete_instance", "host" => host}}) do
Instance.perform(:delete_instance, host)
end
end

View file

@ -0,0 +1,45 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.PollWorker do
@moduledoc """
Generates notifications when a poll ends.
"""
use Pleroma.Workers.WorkerHelper, queue: "poll_notifications"
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
@impl Oban.Worker
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
with %Activity{} = activity <- find_poll_activity(activity_id) do
Notification.create_poll_notifications(activity)
end
end
defp find_poll_activity(activity_id) do
with nil <- Activity.get_by_id(activity_id) do
{:error, :poll_activity_not_found}
end
end
def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
Object.normalize(activity),
{:ok, end_time} <- NaiveDateTime.from_iso8601(closed),
:gt <- NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) do
%{
op: "poll_end",
activity_id: activity_id
}
|> new(scheduled_at: end_time)
|> Oban.insert()
else
_ -> {:error, activity}
end
end
def schedule_poll_end(activity), do: {:error, activity}
end