Merge branch 'release/1.0.5' into 'master'

1.0.5 release

See merge request pleroma/pleroma!1549
This commit is contained in:
kaniini 2019-08-14 02:17:18 +00:00
commit d6da4a75ce
57 changed files with 1140 additions and 257 deletions

View file

@ -3,6 +3,32 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [1.0.5] - 2019-08-13
### Fixed
- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set
- Mastodon API: `muted` in the Status entity, using author's account to determine if the thread was muted
- Mastodon API: return the actual profile URL in the Account entity's `url` property when appropriate
- Templates: properly style anchor tags
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
- Not being able to access the Mastodon FE login page on private instances
- MRF: ensure that subdomain_match calls are case-insensitive
- Fix internal server error when using the healthcheck API.
### Added
- **Breaking:** MRF describe API, which adds support for exposing configuration information about MRF policies to NodeInfo.
Custom modules will need to be updated by adding, at the very least, `def describe, do: {:ok, %{}}` to the MRF policy modules.
- Relays: Added a task to list relay subscriptions.
- MRF: Support for filtering posts based on ActivityStreams vocabulary (`Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`)
- MRF (Simple Policy): Support for wildcard domains.
- Support for wildcard domains in user domain blocks setting.
- Configuration: `quarantined_instances` support wildcard domains.
- Mix Tasks: `mix pleroma.database fix_likes_collections`
- Configuration: `federation_incoming_replies_max_depth` option
### Removed
- Federation: Remove `likes` from objects.
- ActivityPub: The `accept_blocks` configuration setting.
## [1.0.4] - 2019-08-01 ## [1.0.4] - 2019-08-01
### Fixed ### Fixed
- Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag - Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag

View file

@ -220,6 +220,7 @@ config :pleroma, :instance,
}, },
registrations_open: true, registrations_open: true,
federating: true, federating: true,
federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7, federation_reachability_timeout_days: 7,
federation_publisher_modules: [ federation_publisher_modules: [
Pleroma.Web.ActivityPub.Publisher, Pleroma.Web.ActivityPub.Publisher,
@ -300,7 +301,6 @@ config :pleroma, :assets,
default_mascot: :pleroma_fox_tan default_mascot: :pleroma_fox_tan
config :pleroma, :activitypub, config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true, unfollow_blocked: true,
outgoing_blocks: true, outgoing_blocks: true,
follow_handshake_timeout: 500 follow_handshake_timeout: 500
@ -334,6 +334,10 @@ config :pleroma, :mrf_keyword,
config :pleroma, :mrf_subchain, match_actor: %{} config :pleroma, :mrf_subchain, match_actor: %{}
config :pleroma, :mrf_vocabulary,
accept: [],
reject: []
config :pleroma, :rich_media, config :pleroma, :rich_media,
enabled: true, enabled: true,
ignore_hosts: [], ignore_hosts: [],

View file

@ -87,6 +87,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`). * `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in. * `account_activation_required`: Require users to confirm their emails before signing in.
* `federating`: Enable federation with other instances * `federating`: Enable federation with other instances
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it. * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Enable Pleromas Relay, which makes it possible to follow a whole instance * `allow_relay`: Enable Pleromas Relay, which makes it possible to follow a whole instance
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default: * `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
@ -98,6 +99,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section) * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See ``:mrf_rejectnonpublic`` section)
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links. * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (see `:mrf_vocabulary` section)
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. * `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send. * `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json`` * `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
@ -266,6 +268,10 @@ config :pleroma, :mrf_subchain,
* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html) * `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html) * `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
## :mrf_vocabulary
* `accept`: A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.
* `reject`: A list of ActivityStreams terms to reject. If empty, no messages are rejected.
## :media_proxy ## :media_proxy
* `enabled`: Enables proxying of remote media to the instances proxy * `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
@ -319,7 +325,6 @@ config :pleroma, Pleroma.Web.Endpoint,
This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020` This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls starting with `https://example.com:2020`
## :activitypub ## :activitypub
* ``accept_blocks``: Whether to accept incoming block activities from other instances
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed * ``unfollow_blocked``: Whether blocks result in people getting unfollowed
* ``outgoing_blocks``: Whether to federate blocks to other instances * ``outgoing_blocks``: Whether to federate blocks to other instances
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question * ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question

View file

@ -35,6 +35,10 @@ defmodule Mix.Tasks.Pleroma.Database do
## Remove duplicated items from following and update followers count for all users ## Remove duplicated items from following and update followers count for all users
mix pleroma.database update_users_following_followers_counts mix pleroma.database update_users_following_followers_counts
## Fix the pre-existing "likes" collections for all objects
mix pleroma.database fix_likes_collections
""" """
def run(["remove_embedded_objects" | args]) do def run(["remove_embedded_objects" | args]) do
{options, [], []} = {options, [], []} =
@ -119,4 +123,36 @@ defmodule Mix.Tasks.Pleroma.Database do
) )
end end
end end
def run(["fix_likes_collections"]) do
import Ecto.Query
start_pleroma()
from(object in Object,
where: fragment("(?)->>'likes' is not null", object.data),
select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)}
)
|> Pleroma.RepoStreamer.chunk_stream(100)
|> Stream.each(fn objects ->
ids =
objects
|> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end)
|> Enum.map(& &1.id)
Object
|> where([object], object.id in ^ids)
|> update([object],
set: [
data:
fragment(
"jsonb_set(?, '{likes}', '[]'::jsonb, true)",
object.data
)
]
)
|> Repo.update_all([], timeout: :infinity)
end)
|> Stream.run()
end
end end

View file

@ -34,6 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part - `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
- `--uploads-dir` - the directory uploads go in when using a local uploader - `--uploads-dir` - the directory uploads go in when using a local uploader
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.) - `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1
- `--listen-port` - the port the app should listen to, defaults to 4000
""" """
def run(["gen" | rest]) do def run(["gen" | rest]) do
@ -56,7 +58,9 @@ defmodule Mix.Tasks.Pleroma.Instance do
indexable: :string, indexable: :string,
db_configurable: :string, db_configurable: :string,
uploads_dir: :string, uploads_dir: :string,
static_dir: :string static_dir: :string,
listen_ip: :string,
listen_port: :string
], ],
aliases: [ aliases: [
o: :output, o: :output,
@ -146,10 +150,26 @@ defmodule Mix.Tasks.Pleroma.Instance do
"n" "n"
) === "y" ) === "y"
listen_port =
get_option(
options,
:listen_port,
"What port will the app listen to (leave it if you are using the default setup with nginx)?",
4000
)
listen_ip =
get_option(
options,
:listen_ip,
"What ip will the app listen to (leave it if you are using the default setup with nginx)?",
"127.0.0.1"
)
uploads_dir = uploads_dir =
get_option( get_option(
options, options,
:upload_dir, :uploads_dir,
"What directory should media uploads go in (when using the local uploader)?", "What directory should media uploads go in (when using the local uploader)?",
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads]) Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
) )
@ -186,7 +206,9 @@ defmodule Mix.Tasks.Pleroma.Instance do
db_configurable?: db_configurable?, db_configurable?: db_configurable?,
static_dir: static_dir, static_dir: static_dir,
uploads_dir: uploads_dir, uploads_dir: uploads_dir,
rum_enabled: rum_enabled rum_enabled: rum_enabled,
listen_ip: listen_ip,
listen_port: listen_port
) )
result_psql = result_psql =

View file

@ -5,6 +5,7 @@
defmodule Mix.Tasks.Pleroma.Relay do defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task use Mix.Task
import Mix.Pleroma import Mix.Pleroma
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays" @shortdoc "Manages remote relays"
@ -22,6 +23,10 @@ defmodule Mix.Tasks.Pleroma.Relay do
``mix pleroma.relay unfollow <relay_url>`` ``mix pleroma.relay unfollow <relay_url>``
Example: ``mix pleroma.relay unfollow https://example.org/relay`` Example: ``mix pleroma.relay unfollow https://example.org/relay``
## List relay subscriptions
``mix pleroma.relay list``
""" """
def run(["follow", target]) do def run(["follow", target]) do
start_pleroma() start_pleroma()
@ -44,4 +49,19 @@ defmodule Mix.Tasks.Pleroma.Relay do
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}") {:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
end end
end end
def run(["list"]) do
start_pleroma()
with %User{} = user <- Relay.get_actor() do
user.following
|> Enum.each(fn entry ->
URI.parse(entry)
|> Map.get(:host)
|> shell_info()
end)
else
e -> shell_error("Error while fetching relay subscription list: #{inspect(e)}")
end
end
end end

View file

@ -31,8 +31,8 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user invite [OPTION...] mix pleroma.user invite [OPTION...]
Options: Options:
- `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05") - `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
- `--max_use NUMBER` - maximum numbers of token uses - `--max-use NUMBER` - maximum numbers of token uses
## List generated invites ## List generated invites

View file

@ -44,20 +44,20 @@ defmodule Pleroma.Object do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id))) Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end end
def normalize(_, fetch_remote \\ true) def normalize(_, fetch_remote \\ true, options \\ [])
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
# Use this whenever possible, especially when walking graphs in an O(N) loop! # Use this whenever possible, especially when walking graphs in an O(N) loop!
def normalize(%Object{} = object, _), do: object def normalize(%Object{} = object, _, _), do: object
def normalize(%Activity{object: %Object{} = object}, _), do: object def normalize(%Activity{object: %Object{} = object}, _, _), do: object
# A hack for fake activities # A hack for fake activities
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _, _) do
%Object{id: "pleroma:fake_object_id", data: data} %Object{id: "pleroma:fake_object_id", data: data}
end end
# Catch and log Object.normalize() calls where the Activity's child object is not # Catch and log Object.normalize() calls where the Activity's child object is not
# preloaded. # preloaded.
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote, _) do
Logger.debug( Logger.debug(
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
) )
@ -67,7 +67,7 @@ defmodule Pleroma.Object do
normalize(ap_id, fetch_remote) normalize(ap_id, fetch_remote)
end end
def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote, _) do
Logger.debug( Logger.debug(
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!" "Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
) )
@ -78,10 +78,14 @@ defmodule Pleroma.Object do
end end
# Old way, try fetching the object through cache. # Old way, try fetching the object through cache.
def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote) def normalize(%{"id" => ap_id}, fetch_remote, _), do: normalize(ap_id, fetch_remote)
def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) def normalize(ap_id, false, _) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id)
def normalize(_, _), do: nil def normalize(ap_id, true, options) when is_binary(ap_id) do
Fetcher.fetch_object_from_id!(ap_id, options)
end
def normalize(_, _, _), do: nil
# Owned objects can only be mutated by their owner # Owned objects can only be mutated by their owner
def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}), def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),

View file

@ -22,7 +22,7 @@ defmodule Pleroma.Object.Fetcher do
# TODO: # TODO:
# This will create a Create activity, which we need internally at the moment. # This will create a Create activity, which we need internally at the moment.
def fetch_object_from_id(id) do def fetch_object_from_id(id, options \\ []) do
if object = Object.get_cached_by_ap_id(id) do if object = Object.get_cached_by_ap_id(id) do
{:ok, object} {:ok, object}
else else
@ -39,7 +39,7 @@ defmodule Pleroma.Object.Fetcher do
"object" => data "object" => data
}, },
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)}, {:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
{:ok, activity} <- Transmogrifier.handle_incoming(params), {:ok, activity} <- Transmogrifier.handle_incoming(params, options),
{:object, _data, %Object{} = object} <- {:object, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, false)} do {:object, data, Object.normalize(activity, false)} do
{:ok, object} {:ok, object}
@ -69,8 +69,8 @@ defmodule Pleroma.Object.Fetcher do
end end
end end
def fetch_object_from_id!(id) do def fetch_object_from_id!(id, options \\ []) do
with {:ok, object} <- fetch_object_from_id(id) do with {:ok, object} <- fetch_object_from_id(id, options) do
object object
else else
_e -> _e ->

View file

@ -836,15 +836,15 @@ defmodule Pleroma.User do
def mutes?(nil, _), do: false def mutes?(nil, _), do: false
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id) def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
def blocks?(user, %{ap_id: ap_id}) do def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
blocks = user.info.blocks blocks = info.blocks
domain_blocks = user.info.domain_blocks
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks)
%{host: host} = URI.parse(ap_id) %{host: host} = URI.parse(ap_id)
Enum.member?(blocks, ap_id) || Enum.member?(blocks, ap_id) ||
Enum.any?(domain_blocks, fn domain -> Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
host == domain
end)
end end
def subscribed_to?(user, %{ap_id: ap_id}) do def subscribed_to?(user, %{ap_id: ap_id}) do

View file

@ -274,6 +274,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
else else
{:fake, true, activity} -> {:fake, true, activity} ->
{:ok, activity} {:ok, activity}
{:error, message} ->
{:error, message}
end end
end end
@ -730,8 +733,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
from( from(
activity in query, [_activity, object] in query,
where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data) where: fragment("(?)->'likes' \\? (?)", object.data, ^ap_id)
) )
end end

View file

@ -25,4 +25,46 @@ defmodule Pleroma.Web.ActivityPub.MRF do
defp get_policies(policy) when is_atom(policy), do: [policy] defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: [] defp get_policies(_), do: []
@spec subdomains_regex([String.t()]) :: [Regex.t()]
def subdomains_regex(domains) when is_list(domains) do
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
end
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
@callback describe() :: {:ok | :error, Map.t()}
def describe(policies) do
{:ok, policy_configs} =
policies
|> Enum.reduce({:ok, %{}}, fn
policy, {:ok, data} ->
{:ok, policy_data} = policy.describe()
{:ok, Map.merge(data, policy_data)}
_, error ->
error
end)
mrf_policies =
get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
base =
%{
mrf_policies: mrf_policies,
exclusions: length(exclusions) > 0
}
|> Map.merge(policy_configs)
{:ok, base}
end
def describe, do: get_policies() |> describe()
end end

View file

@ -62,4 +62,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
@impl true @impl true
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -5,6 +5,8 @@
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF
require Logger require Logger
# has the user successfully posted before? # has the user successfully posted before?
@ -22,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
defp contains_links?(_), do: false defp contains_links?(_), do: false
@impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor), with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)}, {:contains_links, true} <- {:contains_links, contains_links?(object)},
@ -45,4 +48,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
# in all other cases, pass through # in all other cases, pass through
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -12,4 +12,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
Logger.info("REJECTING #{inspect(object)}") Logger.info("REJECTING #{inspect(object)}")
{:reject, object} {:reject, object}
end end
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -42,4 +42,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
end end
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
def describe, do: {:ok, %{}}
end end

View file

@ -87,4 +87,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
@impl true @impl true
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{mrf_hellthread: Pleroma.Config.get([:mrf_hellthread])}}
end end

View file

@ -94,4 +94,36 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
@impl true @impl true
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe do
# This horror is needed to convert regex sigils to strings
mrf_keyword =
Pleroma.Config.get(:mrf_keyword, [])
|> Enum.map(fn {key, value} ->
{key,
Enum.map(value, fn
{pattern, replacement} ->
%{
"pattern" =>
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end,
"replacement" => replacement
}
pattern ->
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end
end)}
end)
|> Enum.into(%{})
{:ok, %{mrf_keyword: mrf_keyword}}
end
end end

View file

@ -27,4 +27,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
@impl true @impl true
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -10,4 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
def filter(object) do def filter(object) do
{:ok, object} {:ok, object}
end end
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -25,4 +25,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
end end
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
def describe, do: {:ok, %{}}
end end

View file

@ -48,4 +48,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
@impl true @impl true
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
@impl true
def describe, do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get([:mrf_rejectnonpublic])}}
end end

View file

@ -4,22 +4,29 @@
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance" @moduledoc "Filter activities depending on their origin instance"
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour MRF
defp check_accept(%{host: actor_host} = _actor_info, object) do defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts = Pleroma.Config.get([:mrf_simple, :accept]) accepts =
Pleroma.Config.get([:mrf_simple, :accept])
|> MRF.subdomains_regex()
cond do cond do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, object}
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
Enum.member?(accepts, actor_host) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil} true -> {:reject, nil}
end end
end end
defp check_reject(%{host: actor_host} = _actor_info, object) do defp check_reject(%{host: actor_host} = _actor_info, object) do
if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do rejects =
Pleroma.Config.get([:mrf_simple, :reject])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil} {:reject, nil}
else else
{:ok, object} {:ok, object}
@ -31,8 +38,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object %{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
) )
when length(child_attachment) > 0 do when length(child_attachment) > 0 do
media_removal =
Pleroma.Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object = object =
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do if MRF.subdomain_match?(media_removal, actor_host) do
child_object = Map.delete(object["object"], "attachment") child_object = Map.delete(object["object"], "attachment")
Map.put(object, "object", child_object) Map.put(object, "object", child_object)
else else
@ -51,8 +62,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
"object" => child_object "object" => child_object
} = object } = object
) do ) do
media_nsfw =
Pleroma.Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
object = object =
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do if MRF.subdomain_match?(media_nsfw, actor_host) do
tags = (child_object["tag"] || []) ++ ["nsfw"] tags = (child_object["tag"] || []) ++ ["nsfw"]
child_object = Map.put(child_object, "tag", tags) child_object = Map.put(child_object, "tag", tags)
child_object = Map.put(child_object, "sensitive", true) child_object = Map.put(child_object, "sensitive", true)
@ -67,12 +82,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_media_nsfw(_actor_info, object), do: {:ok, object} defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
|> MRF.subdomains_regex()
object = object =
with true <- with true <- MRF.subdomain_match?(timeline_removal, actor_host),
Enum.member?(
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
actor_host
),
user <- User.get_cached_by_ap_id(object["actor"]), user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
to = to =
@ -94,7 +109,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do report_removal =
Pleroma.Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil} {:reject, nil}
else else
{:ok, object} {:ok, object}
@ -104,7 +123,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(_actor_info, object), do: {:ok, object} defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do avatar_removal =
Pleroma.Config.get([:mrf_simple, :avatar_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
{:ok, Map.delete(object, "icon")} {:ok, Map.delete(object, "icon")}
else else
{:ok, object} {:ok, object}
@ -114,7 +137,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(_actor_info, object), do: {:ok, object} defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do banner_removal =
Pleroma.Config.get([:mrf_simple, :banner_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
{:ok, Map.delete(object, "image")} {:ok, Map.delete(object, "image")}
else else
{:ok, object} {:ok, object}
@ -152,4 +179,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end end
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
@impl true
def describe do
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
mrf_simple =
Pleroma.Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
{:ok, %{mrf_simple: mrf_simple}}
end
end end

View file

@ -37,4 +37,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@impl true @impl true
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -149,4 +149,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
@impl true @impl true
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{}}
end end

View file

@ -27,4 +27,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
end end
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
@impl true
def describe do
mrf_user_allowlist =
Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
end
end end

View file

@ -0,0 +1,36 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
@moduledoc "Filter messages which belong to certain activity vocabularies"
@behaviour Pleroma.Web.ActivityPub.MRF
def filter(%{"type" => "Undo", "object" => child_message} = message) do
with {:ok, _} <- filter(child_message) do
{:ok, message}
else
{:reject, nil} ->
{:reject, nil}
end
end
def filter(%{"type" => message_type} = message) do
with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]),
rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]),
true <-
length(accepted_vocabulary) == 0 || Enum.member?(accepted_vocabulary, message_type),
false <-
length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type),
{:ok, _} <- filter(message["object"]) do
{:ok, message}
else
_ -> {:reject, nil}
end
end
def filter(message), do: {:ok, message}
def describe, do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary)}}
end

View file

@ -87,8 +87,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
if public do if public do
true true
else else
inbox_info = URI.parse(inbox) %{host: host} = URI.parse(inbox)
!Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end end
end end

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator
import Ecto.Query import Ecto.Query
@ -22,20 +23,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@doc """ @doc """
Modifies an incoming AP object (mastodon format) to our internal format. Modifies an incoming AP object (mastodon format) to our internal format.
""" """
def fix_object(object) do def fix_object(object, options \\ []) do
object object
|> strip_internal_fields
|> fix_actor |> fix_actor
|> fix_url |> fix_url
|> fix_attachments |> fix_attachments
|> fix_context |> fix_context
|> fix_in_reply_to |> fix_in_reply_to(options)
|> fix_emoji |> fix_emoji
|> fix_tag |> fix_tag
|> fix_content_map |> fix_content_map
|> fix_likes
|> fix_addressing |> fix_addressing
|> fix_summary |> fix_summary
|> fix_type |> fix_type(options)
end end
def fix_summary(%{"summary" => nil} = object) do def fix_summary(%{"summary" => nil} = object) do
@ -150,21 +151,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("actor", Containment.get_actor(%{"actor" => actor})) |> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
end end
# Check for standardisation def fix_in_reply_to(object, options \\ [])
# This is what Peertube does
# curl -H 'Accept: application/activity+json' $likes | jq .totalItems
# Prismo returns only an integer (count) as "likes"
def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
object
|> Map.put("likes", [])
|> Map.put("like_count", 0)
end
def fix_likes(object) do def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
object
end
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object)
when not is_nil(in_reply_to) do when not is_nil(in_reply_to) do
in_reply_to_id = in_reply_to_id =
cond do cond do
@ -182,28 +171,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"" ""
end end
case get_obj_helper(in_reply_to_id) do object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
{:ok, replied_object} ->
with %Activity{} = _activity <-
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
|> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|> Map.put("context", replied_object.data["context"] || object["conversation"])
else
e ->
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
object
end
e -> if Federator.allowed_incoming_reply_depth?(options[:depth]) do
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") case get_obj_helper(in_reply_to_id, options) do
object {:ok, replied_object} ->
with %Activity{} = _activity <-
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
|> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|> Map.put("context", replied_object.data["context"] || object["conversation"])
else
e ->
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
object
end
e ->
Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}")
object
end
else
object
end end
end end
def fix_in_reply_to(object), do: object def fix_in_reply_to(object, _options), do: object
def fix_context(object) do def fix_context(object) do
context = object["context"] || object["conversation"] || Utils.generate_context_id() context = object["context"] || object["conversation"] || Utils.generate_context_id()
@ -336,17 +331,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_content_map(object), do: object def fix_content_map(object), do: object
def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do def fix_type(object, options \\ [])
reply = Object.normalize(reply_id)
if reply && (reply.data["type"] == "Question" and object["name"]) do def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do
reply =
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
{:ok, object} <- get_obj_helper(reply_id, options) do
object
end
if reply && reply.data["type"] == "Question" do
Map.put(object, "type", "Answer") Map.put(object, "type", "Answer")
else else
object object
end end
end end
def fix_type(object), do: object def fix_type(object, _), do: object
defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
with true <- id =~ "follows", with true <- id =~ "follows",
@ -374,9 +376,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
end end
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
# with nil ID. # with nil ID.
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
with context <- data["context"] || Utils.generate_context_id(), with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "", content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor), %User{} = actor <- User.get_cached_by_ap_id(actor),
@ -409,15 +413,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
# disallow objects with bogus IDs # disallow objects with bogus IDs
def handle_incoming(%{"id" => nil}), do: :error def handle_incoming(%{"id" => nil}, _options), do: :error
def handle_incoming(%{"id" => ""}), do: :error def handle_incoming(%{"id" => ""}, _options), do: :error
# length of https:// = 8, should validate better, but good enough for now. # length of https:// = 8, should validate better, but good enough for now.
def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
do: :error
# TODO: validate those with a Ecto scheme # TODO: validate those with a Ecto scheme
# - tags # - tags
# - emoji # - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data) def handle_incoming(
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
actor = Containment.get_actor(data) actor = Containment.get_actor(data)
@ -427,7 +435,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with nil <- Activity.get_create_by_object_ap_id(object["id"]), with nil <- Activity.get_create_by_object_ap_id(object["id"]),
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"]) options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
object = fix_object(data["object"], options)
params = %{ params = %{
to: data["to"], to: data["to"],
@ -452,7 +461,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
def handle_incoming( def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
_options
) do ) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
@ -503,7 +513,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
def handle_incoming( def handle_incoming(
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
_options
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
@ -524,7 +535,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
def handle_incoming( def handle_incoming(
%{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data,
_options
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
@ -548,7 +560,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
def handle_incoming( def handle_incoming(
%{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
_options
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@ -561,7 +574,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
def handle_incoming( def handle_incoming(
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
_options
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@ -576,7 +590,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming( def handle_incoming(
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} = %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
data data,
_options
) )
when object_type in ["Person", "Application", "Service", "Organization"] do when object_type in ["Person", "Application", "Service", "Organization"] do
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
@ -614,7 +629,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# an error or a tombstone. This would allow us to verify that a deletion actually took # an error or a tombstone. This would allow us to verify that a deletion actually took
# place. # place.
def handle_incoming( def handle_incoming(
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data,
_options
) do ) do
object_id = Utils.get_ap_id(object_id) object_id = Utils.get_ap_id(object_id)
@ -645,7 +661,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Announce", "object" => object_id}, "object" => %{"type" => "Announce", "object" => object_id},
"actor" => _actor, "actor" => _actor,
"id" => id "id" => id
} = data } = data,
_options
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@ -663,7 +680,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Follow", "object" => followed}, "object" => %{"type" => "Follow", "object" => followed},
"actor" => follower, "actor" => follower,
"id" => id "id" => id
} = _data } = _data,
_options
) do ) do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
@ -681,10 +699,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Block", "object" => blocked}, "object" => %{"type" => "Block", "object" => blocked},
"actor" => blocker, "actor" => blocker,
"id" => id "id" => id
} = _data } = _data,
_options
) do ) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]), with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker), {:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
User.unblock(blocker, blocked) User.unblock(blocker, blocked)
@ -695,10 +713,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
def handle_incoming( def handle_incoming(
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
_options
) do ) do
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]), with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker), {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
User.unfollow(blocker, blocked) User.unfollow(blocker, blocked)
@ -715,7 +733,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
"object" => %{"type" => "Like", "object" => object_id}, "object" => %{"type" => "Like", "object" => object_id},
"actor" => _actor, "actor" => _actor,
"id" => id "id" => id
} = data } = data,
_options
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
@ -727,10 +746,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end end
end end
def handle_incoming(_), do: :error def handle_incoming(_, _), do: :error
def get_obj_helper(id) do def get_obj_helper(id, options \\ []) do
if object = Object.normalize(id), do: {:ok, object}, else: nil if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
end end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
@ -752,7 +771,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> add_mention_tags |> add_mention_tags
|> add_emoji_tags |> add_emoji_tags
|> add_attributed_to |> add_attributed_to
|> add_likes
|> prepare_attachments |> prepare_attachments
|> set_conversation |> set_conversation
|> set_reply_to_uri |> set_reply_to_uri
@ -936,22 +954,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("attributedTo", attributed_to) |> Map.put("attributedTo", attributed_to)
end end
def add_likes(%{"id" => id, "like_count" => likes} = object) do
likes = %{
"id" => "#{id}/likes",
"first" => "#{id}/likes?page=1",
"type" => "OrderedCollection",
"totalItems" => likes
}
object
|> Map.put("likes", likes)
end
def add_likes(object) do
object
end
def prepare_attachments(object) do def prepare_attachments(object) do
attachments = attachments =
(object["attachment"] || []) (object["attachment"] || [])
@ -967,6 +969,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_fields(object) do defp strip_internal_fields(object) do
object object
|> Map.drop([ |> Map.drop([
"likes",
"like_count", "like_count",
"announcements", "announcements",
"announcement_count", "announcement_count",

View file

@ -251,20 +251,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def insert_full_object(map), do: {:ok, map, nil} def insert_full_object(map), do: {:ok, map, nil}
def update_object_in_activities(%{data: %{"id" => id}} = object) do
# TODO
# Update activities that already had this. Could be done in a seperate process.
# Alternatively, just don't do this and fetch the current object each time. Most
# could probably be taken from cache.
relevant_activities = Activity.get_all_create_by_object_ap_id(id)
Enum.map(relevant_activities, fn activity ->
new_activity_data = activity.data |> Map.put("object", object.data)
changeset = Changeset.change(activity, data: new_activity_data)
Repo.update(changeset)
end)
end
#### Like-related helpers #### Like-related helpers
@doc """ @doc """
@ -347,8 +333,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put("#{property}_count", length(element)) |> Map.put("#{property}_count", length(element))
|> Map.put("#{property}s", element), |> Map.put("#{property}s", element),
changeset <- Changeset.change(object, data: new_data), changeset <- Changeset.change(object, data: new_data),
{:ok, object} <- Object.update_and_set_cache(changeset), {:ok, object} <- Object.update_and_set_cache(changeset) do
_ <- update_object_in_activities(object) do
{:ok, object} {:ok, object}
end end
end end

View file

@ -22,6 +22,18 @@ defmodule Pleroma.Web.Federator do
refresh_subscriptions() refresh_subscriptions()
end end
@doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)"
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
def allowed_incoming_reply_depth?(depth) do
max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
if max_replies_depth do
(depth || 1) <= max_replies_depth
else
true
end
end
# Client API # Client API
def incoming_doc(doc) do def incoming_doc(doc) do

View file

@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
id: to_string(user.id), id: to_string(user.id),
acct: user.nickname, acct: user.nickname,
username: username_from_nickname(user.nickname), username: username_from_nickname(user.nickname),
url: user.ap_id url: User.profile_url(user)
} }
end end
@ -71,6 +71,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
image = User.avatar_url(user) |> MediaProxy.url() image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url() header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user) user_info = User.get_cached_user_info(user)
following_count =
((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0
followers_count =
((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0
bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"] bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]
emojis = emojis =
@ -101,11 +108,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
display_name: display_name, display_name: display_name,
locked: user_info.locked, locked: user_info.locked,
created_at: Utils.to_masto_date(user.inserted_at), created_at: Utils.to_masto_date(user.inserted_at),
followers_count: user_info.follower_count, followers_count: followers_count,
following_count: user_info.following_count, following_count: following_count,
statuses_count: user_info.note_count, statuses_count: user_info.note_count,
note: bio || "", note: bio || "",
url: user.ap_id, url: User.profile_url(user),
avatar: image, avatar: image,
avatar_static: image, avatar_static: image,
header: header, header: header,

View file

@ -160,7 +160,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
thread_muted? = thread_muted? =
case activity.thread_muted? do case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted? thread_muted? when is_boolean(thread_muted?) -> thread_muted?
nil -> CommonAPI.thread_muted?(user, activity) nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false
end end
attachment_data = object.data["attachment"] || [] attachment_data = object.data["attachment"] || []

View file

@ -34,64 +34,18 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
def raw_nodeinfo do def raw_nodeinfo do
stats = Stats.get_stats() stats = Stats.get_stats()
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
mrf_simple =
Config.get(:mrf_simple)
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|> Enum.into(%{})
# This horror is needed to convert regex sigils to strings
mrf_keyword =
Config.get(:mrf_keyword, [])
|> Enum.map(fn {key, value} ->
{key,
Enum.map(value, fn
{pattern, replacement} ->
%{
"pattern" =>
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end,
"replacement" => replacement
}
pattern ->
if not is_binary(pattern) do
inspect(pattern)
else
pattern
end
end)}
end)
|> Enum.into(%{})
mrf_policies =
MRF.get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
quarantined = Config.get([:instance, :quarantined_instances], []) quarantined = Config.get([:instance, :quarantined_instances], [])
staff_accounts = staff_accounts =
User.all_superusers() User.all_superusers()
|> Enum.map(fn u -> u.ap_id end) |> Enum.map(fn u -> u.ap_id end)
mrf_user_allowlist =
Config.get([:mrf_user_allowlist], [])
|> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)
federation_response = federation_response =
if Config.get([:instance, :mrf_transparency]) do if Config.get([:instance, :mrf_transparency]) do
%{ {:ok, data} = MRF.describe()
mrf_policies: mrf_policies,
mrf_simple: mrf_simple, data
mrf_keyword: mrf_keyword, |> Map.merge(%{quarantined_instances: quarantined})
mrf_user_allowlist: mrf_user_allowlist,
quarantined_instances: quarantined,
exclusions: length(exclusions) > 0
}
else else
%{} %{}
end end

View file

@ -182,6 +182,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: [] author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
retweeted_object = Object.normalize(retweeted_activity)
retweeted_user = User.get_cached_by_ap_id(retweeted_activity.data["actor"]) retweeted_user = User.get_cached_by_ap_id(retweeted_activity.data["actor"])
retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true) retweeted_xml = to_simple_form(retweeted_activity, retweeted_user, true)
@ -196,7 +197,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
{:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']}, {:"activity:verb", ['http://activitystrea.ms/schema/1.0/share']},
{:id, h.(activity.data["id"])}, {:id, h.(activity.data["id"])},
{:title, ['#{user.nickname} repeated a notice']}, {:title, ['#{user.nickname} repeated a notice']},
{:content, [type: 'html'], ['RT #{retweeted_activity.data["object"]["content"]}']}, {:content, [type: 'html'], ['RT #{retweeted_object.data["content"]}']},
{:published, h.(inserted_at)}, {:published, h.(inserted_at)},
{:updated, h.(updated_at)}, {:updated, h.(updated_at)},
{:"ostatus:conversation", [ref: h.(activity.data["context"])], {:"ostatus:conversation", [ref: h.(activity.data["context"])],

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.XML alias Pleroma.Web.XML
@ -88,14 +89,15 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
Map.put(note, "external_url", url) Map.put(note, "external_url", url)
end end
def fetch_replied_to_activity(entry, in_reply_to) do def fetch_replied_to_activity(entry, in_reply_to, options \\ []) do
with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do
activity activity
else else
_e -> _e ->
with in_reply_to_href when not is_nil(in_reply_to_href) <- with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
in_reply_to_href when not is_nil(in_reply_to_href) <-
XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry), XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry),
{:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href) do {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href, options) do
activity activity
else else
_e -> nil _e -> nil
@ -104,7 +106,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
end end
# TODO: Clean this up a bit. # TODO: Clean this up a bit.
def handle_note(entry, doc \\ nil) do def handle_note(entry, doc \\ nil, options \\ []) do
with id <- XML.string_from_xpath("//id", entry), with id <- XML.string_from_xpath("//id", entry),
activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id), activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id),
[author] <- :xmerl_xpath.string('//author[1]', doc), [author] <- :xmerl_xpath.string('//author[1]', doc),
@ -112,7 +114,8 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
content_html <- OStatus.get_content(entry), content_html <- OStatus.get_content(entry),
cw <- OStatus.get_cw(entry), cw <- OStatus.get_cw(entry),
in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry), in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),
in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to), options <- Keyword.put(options, :depth, (options[:depth] || 0) + 1),
in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to, options),
in_reply_to_object <- in_reply_to_object <-
(in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil, (in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil,
in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to, in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,

View file

@ -54,7 +54,7 @@ defmodule Pleroma.Web.OStatus do
"#{Web.base_url()}/ostatus_subscribe?acct={uri}" "#{Web.base_url()}/ostatus_subscribe?acct={uri}"
end end
def handle_incoming(xml_string) do def handle_incoming(xml_string, options \\ []) do
with doc when doc != :error <- parse_document(xml_string) do with doc when doc != :error <- parse_document(xml_string) do
with {:ok, actor_user} <- find_make_or_update_actor(doc), with {:ok, actor_user} <- find_make_or_update_actor(doc),
do: Pleroma.Instances.set_reachable(actor_user.ap_id) do: Pleroma.Instances.set_reachable(actor_user.ap_id)
@ -91,10 +91,12 @@ defmodule Pleroma.Web.OStatus do
_ -> _ ->
case object_type do case object_type do
'http://activitystrea.ms/schema/1.0/note' -> 'http://activitystrea.ms/schema/1.0/note' ->
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
do: activity
'http://activitystrea.ms/schema/1.0/comment' -> 'http://activitystrea.ms/schema/1.0/comment' ->
with {:ok, activity} <- NoteHandler.handle_note(entry, doc), do: activity with {:ok, activity} <- NoteHandler.handle_note(entry, doc, options),
do: activity
_ -> _ ->
Logger.error("Couldn't parse incoming document") Logger.error("Couldn't parse incoming document")
@ -366,7 +368,7 @@ defmodule Pleroma.Web.OStatus do
end end
end end
def fetch_activity_from_atom_url(url) do def fetch_activity_from_atom_url(url, options \\ []) do
with true <- String.starts_with?(url, "http"), with true <- String.starts_with?(url, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <- {:ok, %{body: body, status: code}} when code in 200..299 <-
HTTP.get( HTTP.get(
@ -374,7 +376,7 @@ defmodule Pleroma.Web.OStatus do
[{:Accept, "application/atom+xml"}] [{:Accept, "application/atom+xml"}]
) do ) do
Logger.debug("Got document from #{url}, handling...") Logger.debug("Got document from #{url}, handling...")
handle_incoming(body) handle_incoming(body, options)
else else
e -> e ->
Logger.debug("Couldn't get #{url}: #{inspect(e)}") Logger.debug("Couldn't get #{url}: #{inspect(e)}")
@ -382,13 +384,13 @@ defmodule Pleroma.Web.OStatus do
end end
end end
def fetch_activity_from_html_url(url) do def fetch_activity_from_html_url(url, options \\ []) do
Logger.debug("Trying to fetch #{url}") Logger.debug("Trying to fetch #{url}")
with true <- String.starts_with?(url, "http"), with true <- String.starts_with?(url, "http"),
{:ok, %{body: body}} <- HTTP.get(url, []), {:ok, %{body: body}} <- HTTP.get(url, []),
{:ok, atom_url} <- get_atom_url(body) do {:ok, atom_url} <- get_atom_url(body) do
fetch_activity_from_atom_url(atom_url) fetch_activity_from_atom_url(atom_url, options)
else else
e -> e ->
Logger.debug("Couldn't get #{url}: #{inspect(e)}") Logger.debug("Couldn't get #{url}: #{inspect(e)}")
@ -396,11 +398,11 @@ defmodule Pleroma.Web.OStatus do
end end
end end
def fetch_activity_from_url(url) do def fetch_activity_from_url(url, options \\ []) do
with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url) do with {:ok, [_ | _] = activities} <- fetch_activity_from_atom_url(url, options) do
{:ok, activities} {:ok, activities}
else else
_e -> fetch_activity_from_html_url(url) _e -> fetch_activity_from_html_url(url, options)
end end
rescue rescue
e -> e ->

View file

@ -684,7 +684,7 @@ defmodule Pleroma.Web.Router do
delete("/auth/sign_out", MastodonAPIController, :logout) delete("/auth/sign_out", MastodonAPIController, :logout)
scope [] do scope [] do
pipe_through(:oauth_read_or_public) pipe_through(:oauth_read)
get("/web/*path", MastodonAPIController, :index) get("/web/*path", MastodonAPIController, :index)
end end
end end

View file

@ -36,6 +36,11 @@
margin-bottom: 20px; margin-bottom: 20px;
} }
a {
color: color: #d8a070;
text-decoration: none;
}
form { form {
width: 100%; width: 100%;
} }

View file

@ -9,7 +9,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Healthcheck
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
@ -22,7 +24,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do with %User{} = user <- User.get_cached_by_nickname(nick),
avatar = User.avatar_url(user) do
conn conn
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
else else
@ -335,20 +338,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end end
def healthcheck(conn, _params) do def healthcheck(conn, _params) do
info = with true <- Config.get([:instance, :healthcheck]),
if Pleroma.Config.get([:instance, :healthcheck]) do %{healthy: true} = info <- Healthcheck.system_info() do
Pleroma.Healthcheck.system_info() json(conn, info)
else else
%{} %{healthy: false} = info ->
end service_unavailable(conn, info)
conn = _ ->
if info[:healthy] do service_unavailable(conn, %{})
conn end
else end
Plug.Conn.put_status(conn, :service_unavailable)
end
json(conn, info) defp service_unavailable(conn, info) do
conn
|> put_status(:service_unavailable)
|> json(info)
end end
end end

View file

@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do def project do
[ [
app: :pleroma, app: :pleroma,
version: version("1.0.4"), version: version("1.0.5"),
elixir: "~> 1.7", elixir: "~> 1.7",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),

View file

@ -11,6 +11,7 @@ end %>
config :pleroma, Pleroma.Web.Endpoint, config :pleroma, Pleroma.Web.Endpoint,
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>], url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
http: [ip: {<%= String.replace(listen_ip, ".", ", ") %>}, port: <%= listen_port %>],
secret_key_base: "<%= secret %>", secret_key_base: "<%= secret %>",
signing_salt: "<%= signing_salt %>" signing_salt: "<%= signing_salt %>"

View file

@ -0,0 +1,13 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule MRFModuleMock do
@behaviour Pleroma.Web.ActivityPub.MRF
@impl true
def filter(message), do: {:ok, message}
@impl true
def describe, do: {:ok, %{mrf_module_mock: "some config data"}}
end

View file

@ -3,8 +3,11 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.DatabaseTest do defmodule Mix.Tasks.Pleroma.DatabaseTest do
alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
@ -46,4 +49,37 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
assert user.info.follower_count == 0 assert user.info.follower_count == 0
end end
end end
describe "running fix_likes_collections" do
test "it turns OrderedCollection likes into empty arrays" do
[user, user2] = insert_pair(:user)
{:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})
{:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"})
CommonAPI.favorite(id, user2)
likes = %{
"first" =>
"http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
"id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
"totalItems" => 3,
"type" => "OrderedCollection"
}
new_data = Map.put(object2.data, "likes", likes)
object2
|> Ecto.Changeset.change(%{data: new_data})
|> Repo.update()
assert length(Object.get_by_id(object.id).data["likes"]) == 1
assert is_map(Object.get_by_id(object2.id).data["likes"])
assert :ok == Mix.Tasks.Pleroma.Database.run(["fix_likes_collections"])
assert length(Object.get_by_id(object.id).data["likes"]) == 1
assert Enum.empty?(Object.get_by_id(object2.id).data["likes"])
end
end
end end

View file

@ -38,7 +38,17 @@ defmodule Pleroma.InstanceTest do
"--indexable", "--indexable",
"y", "y",
"--db-configurable", "--db-configurable",
"y" "y",
"--rum",
"y",
"--listen-port",
"4000",
"--listen-ip",
"127.0.0.1",
"--uploads-dir",
"test/uploads",
"--static-dir",
"instance/static/"
]) ])
end end
@ -56,10 +66,11 @@ defmodule Pleroma.InstanceTest do
assert generated_config =~ "username: \"dbuser\"" assert generated_config =~ "username: \"dbuser\""
assert generated_config =~ "password: \"dbpass\"" assert generated_config =~ "password: \"dbpass\""
assert generated_config =~ "dynamic_configuration: true" assert generated_config =~ "dynamic_configuration: true"
assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql() assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
end end
defp generated_setup_psql do defp generated_setup_psql do
~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n) ~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\nCREATE EXTENSION IF NOT EXISTS rum;\n)
end end
end end

View file

@ -799,6 +799,48 @@ defmodule Pleroma.UserTest do
assert User.blocks?(user, collateral_user) assert User.blocks?(user, collateral_user)
end end
test "does not block domain with same end" do
user = insert(:user)
collateral_user =
insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
refute User.blocks?(user, collateral_user)
end
test "does not block domain with same end if wildcard added" do
user = insert(:user)
collateral_user =
insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
refute User.blocks?(user, collateral_user)
end
test "blocks domain with wildcard for subdomain" do
user = insert(:user)
user_from_subdomain =
insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"})
user_with_two_subdomains =
insert(:user, %{
ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully"
})
user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
assert User.blocks?(user, user_from_subdomain)
assert User.blocks?(user, user_with_two_subdomains)
assert User.blocks?(user, user_domain)
end
test "unblocks domains" do test "unblocks domains" do
user = insert(:user) user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})

View file

@ -679,9 +679,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert like_activity == same_like_activity assert like_activity == same_like_activity
assert object.data["likes"] == [user.ap_id] assert object.data["likes"] == [user.ap_id]
[note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])
assert note_activity.data["object"]["like_count"] == 1
{:ok, _like_activity, object} = ActivityPub.like(user_two, object) {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
assert object.data["like_count"] == 2 assert object.data["like_count"] == 2
end end

View file

@ -0,0 +1,86 @@
defmodule Pleroma.Web.ActivityPub.MRFTest do
use ExUnit.Case, async: true
alias Pleroma.Web.ActivityPub.MRF
test "subdomains_regex/1" do
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
~r/^unsafe.tld$/i,
~r/^(.*\.)*unsafe.tld$/i
]
end
describe "subdomain_match/2" do
test "common domains" do
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
refute MRF.subdomain_match?(regexes, "example.com")
end
test "wildcard domains with one subdomain" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
refute MRF.subdomain_match?(regexes, "anotherunsafe.tld")
refute MRF.subdomain_match?(regexes, "unsafe.tldanother")
end
test "wildcard domains with two subdomains" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
end
test "matches are case-insensitive" do
regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i]
assert MRF.subdomain_match?(regexes, "UNSAFE.TLD")
assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD")
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
refute MRF.subdomain_match?(regexes, "EXAMPLE.COM")
refute MRF.subdomain_match?(regexes, "example.com")
end
end
describe "describe/0" do
test "it works as expected with noop policy" do
expected = %{
mrf_policies: ["NoOpPolicy"],
exclusions: false
}
{:ok, ^expected} = MRF.describe()
end
test "it works as expected with mock policy" do
config = Pleroma.Config.get([:instance, :rewrite_policy])
Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock])
expected = %{
mrf_policies: ["MRFModuleMock"],
mrf_module_mock: "some config data",
exclusions: false
}
{:ok, ^expected} = MRF.describe()
Pleroma.Config.put([:instance, :rewrite_policy], config)
end
end
end

View file

@ -49,6 +49,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :media_removal], ["*.remote.instance"])
media_message = build_media_message()
local_message = build_local_message()
assert SimplePolicy.filter(media_message) ==
{:ok,
media_message
|> Map.put("object", Map.delete(media_message["object"], "attachment"))}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
end end
describe "when :media_nsfw" do describe "when :media_nsfw" do
@ -74,6 +87,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"])
media_message = build_media_message()
local_message = build_local_message()
assert SimplePolicy.filter(media_message) ==
{:ok,
media_message
|> put_in(["object", "tag"], ["foo", "nsfw"])
|> put_in(["object", "sensitive"], true)}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
end end
defp build_media_message do defp build_media_message do
@ -106,6 +133,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(report_message) == {:reject, nil} assert SimplePolicy.filter(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :report_removal], ["*.remote.instance"])
report_message = build_report_message()
local_message = build_local_message()
assert SimplePolicy.filter(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
end end
defp build_report_message do defp build_report_message do
@ -146,6 +182,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
{actor, ftl_message} = build_ftl_actor_and_message()
ftl_message_actor_host =
ftl_message
|> Map.fetch!("actor")
|> URI.parse()
|> Map.fetch!(:host)
Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host])
local_message = build_local_message()
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
assert actor.follower_address in ftl_message["to"]
refute actor.follower_address in ftl_message["cc"]
refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
test "has a matching host but only as:Public in to" do test "has a matching host but only as:Public in to" do
{_actor, ftl_message} = build_ftl_actor_and_message() {_actor, ftl_message} = build_ftl_actor_and_message()
@ -192,6 +249,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(remote_message) == {:reject, nil} assert SimplePolicy.filter(remote_message) == {:reject, nil}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :reject], ["*.remote.instance"])
remote_message = build_remote_message()
assert SimplePolicy.filter(remote_message) == {:reject, nil}
end
end end
describe "when :accept" do describe "when :accept" do
@ -224,6 +289,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message} assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :accept], ["*.remote.instance"])
local_message = build_local_message()
remote_message = build_remote_message()
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end
end end
describe "when :avatar_removal" do describe "when :avatar_removal" do
@ -251,6 +326,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
refute filtered["icon"] refute filtered["icon"]
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["icon"]
end
end end
describe "when :banner_removal" do describe "when :banner_removal" do
@ -278,6 +362,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
refute filtered["image"] refute filtered["image"]
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["image"]
end
end end
defp build_local_message do defp build_local_message do

View file

@ -0,0 +1,123 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do
use Pleroma.DataCase
alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy
describe "accept" do
test "it accepts based on parent activity type" do
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"])
message = %{
"type" => "Like",
"object" => "whatever"
}
{:ok, ^message} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
end
test "it accepts based on child object type" do
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
message = %{
"type" => "Create",
"object" => %{
"type" => "Note",
"content" => "whatever"
}
}
{:ok, ^message} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
end
test "it does not accept disallowed child objects" do
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"])
message = %{
"type" => "Create",
"object" => %{
"type" => "Article",
"content" => "whatever"
}
}
{:reject, nil} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
end
test "it does not accept disallowed parent types" do
config = Pleroma.Config.get([:mrf_vocabulary, :accept])
Pleroma.Config.put([:mrf_vocabulary, :accept], ["Announce", "Note"])
message = %{
"type" => "Create",
"object" => %{
"type" => "Note",
"content" => "whatever"
}
}
{:reject, nil} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :accept], config)
end
end
describe "reject" do
test "it rejects based on parent activity type" do
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
message = %{
"type" => "Like",
"object" => "whatever"
}
{:reject, nil} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
end
test "it rejects based on child object type" do
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Note"])
message = %{
"type" => "Create",
"object" => %{
"type" => "Note",
"content" => "whatever"
}
}
{:reject, nil} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
end
test "it passes through objects that aren't disallowed" do
config = Pleroma.Config.get([:mrf_vocabulary, :reject])
Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"])
message = %{
"type" => "Announce",
"object" => "whatever"
}
{:ok, ^message} = VocabularyPolicy.filter(message)
Pleroma.Config.put([:mrf_vocabulary, :reject], config)
end
end
end

View file

@ -11,12 +11,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.Websub.WebsubClientSubscription alias Pleroma.Web.Websub.WebsubClientSubscription
import Mock
import Pleroma.Factory import Pleroma.Factory
import ExUnit.CaptureLog import ExUnit.CaptureLog
alias Pleroma.Web.CommonAPI
setup_all do setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -46,12 +47,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
data["object"] data["object"]
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873") |> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
data = data = Map.put(data, "object", object)
data
|> Map.put("object", object)
{:ok, returned_activity} = Transmogrifier.handle_incoming(data) {:ok, returned_activity} = Transmogrifier.handle_incoming(data)
returned_object = Object.normalize(returned_activity.data["object"])
returned_object = Object.normalize(returned_activity.data["object"], false)
assert activity = assert activity =
Activity.get_create_by_object_ap_id( Activity.get_create_by_object_ap_id(
@ -61,6 +60,32 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
end end
test "it does not fetch replied-to activities beyond max_replies_depth" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
object =
data["object"]
|> Map.put("inReplyTo", "https://shitposter.club/notice/2827873")
data = Map.put(data, "object", object)
with_mock Pleroma.Web.Federator,
allowed_incoming_reply_depth?: fn _ -> false end do
{:ok, returned_activity} = Transmogrifier.handle_incoming(data)
returned_object = Object.normalize(returned_activity.data["object"], false)
refute Activity.get_create_by_object_ap_id(
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
)
assert returned_object.data["inReplyToAtomUri"] ==
"https://shitposter.club/notice/2827873"
end
end
test "it does not crash if the object in inReplyTo can't be fetched" do test "it does not crash if the object in inReplyTo can't be fetched" do
data = data =
File.read!("test/fixtures/mastodon-post-activity.json") File.read!("test/fixtures/mastodon-post-activity.json")
@ -424,6 +449,27 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert !is_nil(data["cc"]) assert !is_nil(data["cc"])
end end
test "it strips internal likes" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
likes = %{
"first" =>
"http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1",
"id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes",
"totalItems" => 3,
"type" => "OrderedCollection"
}
object = Map.put(data["object"], "likes", likes)
data = Map.put(data, "object", object)
{:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data)
refute Map.has_key?(object.data, "likes")
end
test "it works for incoming update activities" do test "it works for incoming update activities" do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
@ -1010,14 +1056,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert is_nil(modified["object"]["announcements"]) assert is_nil(modified["object"]["announcements"])
assert is_nil(modified["object"]["announcement_count"]) assert is_nil(modified["object"]["announcement_count"])
assert is_nil(modified["object"]["context_id"]) assert is_nil(modified["object"]["context_id"])
end assert is_nil(modified["object"]["likes"])
test "it adds like collection to object" do
activity = insert(:note_activity)
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["object"]["likes"]["type"] == "OrderedCollection"
assert modified["object"]["likes"]["totalItems"] == 0
end end
test "the directMessage flag is present" do test "the directMessage flag is present" do

View file

@ -213,5 +213,21 @@ defmodule Pleroma.Web.FederatorTest do
:error = Federator.incoming_ap_doc(params) :error = Federator.incoming_ap_doc(params)
end end
test "it does not crash if MRF rejects the post" do
policies = Pleroma.Config.get([:instance, :rewrite_policy])
mrf_keyword_policy = Pleroma.Config.get(:mrf_keyword)
Pleroma.Config.put([:mrf_keyword, :reject], ["lain"])
Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.KeywordPolicy)
params =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
assert Federator.incoming_ap_doc(params) == :error
Pleroma.Config.put([:instance, :rewrite_policy], policies)
Pleroma.Config.put(:mrf_keyword, mrf_keyword_policy)
end
end end
end end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
test "Represent a user account" do test "Represent a user account" do
@ -275,4 +276,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
result = AccountView.render("account.json", %{user: user}) result = AccountView.render("account.json", %{user: user})
refute result.display_name == "<marquee> username </marquee>" refute result.display_name == "<marquee> username </marquee>"
end end
describe "hiding follows/following" do
test "shows when follows/following are hidden and sets follower/following count to 0" do
user = insert(:user, info: %{hide_followers: true, hide_follows: true})
other_user = insert(:user)
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert %{
followers_count: 0,
following_count: 0,
pleroma: %{hide_follows: true, hide_followers: true}
} = AccountView.render("account.json", %{user: user})
end
test "shows actual follower/following count to the account owner" do
user = insert(:user, info: %{hide_followers: true, hide_follows: true})
other_user = insert(:user)
{:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user)
{:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
assert %{
followers_count: 1,
following_count: 1
} = AccountView.render("account.json", %{user: user, for: user})
end
end
end end

View file

@ -2684,8 +2684,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
describe "conversation muting" do describe "conversation muting" do
setup do setup do
post_user = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
{:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"})
[user: user, activity: activity] [user: user, activity: activity]
end end
@ -2877,6 +2879,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert redirected_to(conn) == "/web/login" assert redirected_to(conn) == "/web/login"
end end
test "redirects not logged-in users to the login page on private instances", %{
conn: conn,
path: path
} do
is_public = Pleroma.Config.get([:instance, :public])
Pleroma.Config.put([:instance, :public], false)
conn = get(conn, path)
assert conn.status == 302
assert redirected_to(conn) == "/web/login"
Pleroma.Config.put([:instance, :public], is_public)
end
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
token = insert(:oauth_token) token = insert(:oauth_token)

View file

@ -85,6 +85,9 @@ defmodule Pleroma.Web.NodeInfoTest do
end end
test "it shows MRF transparency data if enabled", %{conn: conn} do test "it shows MRF transparency data if enabled", %{conn: conn} do
config = Pleroma.Config.get([:instance, :rewrite_policy])
Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
option = Pleroma.Config.get([:instance, :mrf_transparency]) option = Pleroma.Config.get([:instance, :mrf_transparency])
Pleroma.Config.put([:instance, :mrf_transparency], true) Pleroma.Config.put([:instance, :mrf_transparency], true)
@ -98,11 +101,15 @@ defmodule Pleroma.Web.NodeInfoTest do
assert response["metadata"]["federation"]["mrf_simple"] == simple_config assert response["metadata"]["federation"]["mrf_simple"] == simple_config
Pleroma.Config.put([:instance, :rewrite_policy], config)
Pleroma.Config.put([:instance, :mrf_transparency], option) Pleroma.Config.put([:instance, :mrf_transparency], option)
Pleroma.Config.put(:mrf_simple, %{}) Pleroma.Config.put(:mrf_simple, %{})
end end
test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
config = Pleroma.Config.get([:instance, :rewrite_policy])
Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
option = Pleroma.Config.get([:instance, :mrf_transparency]) option = Pleroma.Config.get([:instance, :mrf_transparency])
Pleroma.Config.put([:instance, :mrf_transparency], true) Pleroma.Config.put([:instance, :mrf_transparency], true)
@ -122,6 +129,7 @@ defmodule Pleroma.Web.NodeInfoTest do
assert response["metadata"]["federation"]["mrf_simple"] == expected_config assert response["metadata"]["federation"]["mrf_simple"] == expected_config
assert response["metadata"]["federation"]["exclusions"] == true assert response["metadata"]["federation"]["exclusions"] == true
Pleroma.Config.put([:instance, :rewrite_policy], config)
Pleroma.Config.put([:instance, :mrf_transparency], option) Pleroma.Config.put([:instance, :mrf_transparency], option)
Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions) Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)
Pleroma.Config.put(:mrf_simple, %{}) Pleroma.Config.put(:mrf_simple, %{})

View file

@ -11,8 +11,10 @@ defmodule Pleroma.Web.OStatusTest do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
alias Pleroma.Web.XML alias Pleroma.Web.XML
import Pleroma.Factory
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Mock
import Pleroma.Factory
setup_all do setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -196,7 +198,7 @@ defmodule Pleroma.Web.OStatusTest do
assert retweeted_activity.data["type"] == "Create" assert retweeted_activity.data["type"] == "Create"
assert retweeted_activity.data["actor"] == user.ap_id assert retweeted_activity.data["actor"] == user.ap_id
assert retweeted_activity.local assert retweeted_activity.local
assert retweeted_activity.data["object"]["announcement_count"] == 1 assert Object.normalize(retweeted_activity).data["announcement_count"] == 1
end end
test "handle incoming retweets - Mastodon, salmon" do test "handle incoming retweets - Mastodon, salmon" do
@ -266,10 +268,13 @@ defmodule Pleroma.Web.OStatusTest do
assert favorited_activity.local assert favorited_activity.local
end end
test "handle incoming replies" do test_with_mock "handle incoming replies, fetching replied-to activities if we don't have them",
OStatus,
[:passthrough],
[] do
incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml") incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
{:ok, [activity]} = OStatus.handle_incoming(incoming) {:ok, [activity]} = OStatus.handle_incoming(incoming)
object = Object.normalize(activity.data["object"]) object = Object.normalize(activity.data["object"], false)
assert activity.data["type"] == "Create" assert activity.data["type"] == "Create"
assert object.data["type"] == "Note" assert object.data["type"] == "Note"
@ -282,6 +287,23 @@ defmodule Pleroma.Web.OStatusTest do
assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note" assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note"
assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"]
assert called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
end
test_with_mock "handle incoming replies, not fetching replied-to activities beyond max_replies_depth",
OStatus,
[:passthrough],
[] do
incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml")
with_mock Pleroma.Web.Federator,
allowed_incoming_reply_depth?: fn _ -> false end do
{:ok, [activity]} = OStatus.handle_incoming(incoming)
object = Object.normalize(activity.data["object"], false)
refute called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_))
end
end end
test "handle incoming follows" do test "handle incoming follows" do

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import Pleroma.Factory import Pleroma.Factory
import Mock
setup do setup do
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -227,10 +228,67 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
end end
end end
test "GET /api/pleroma/healthcheck", %{conn: conn} do describe "GET /api/pleroma/healthcheck" do
conn = get(conn, "/api/pleroma/healthcheck") setup do
config_healthcheck = Pleroma.Config.get([:instance, :healthcheck])
assert conn.status in [200, 503] on_exit(fn ->
Pleroma.Config.put([:instance, :healthcheck], config_healthcheck)
end)
:ok
end
test "returns 503 when healthcheck disabled", %{conn: conn} do
Pleroma.Config.put([:instance, :healthcheck], false)
response =
conn
|> get("/api/pleroma/healthcheck")
|> json_response(503)
assert response == %{}
end
test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do
Pleroma.Config.put([:instance, :healthcheck], true)
with_mock Pleroma.Healthcheck,
system_info: fn -> %Pleroma.Healthcheck{healthy: true} end do
response =
conn
|> get("/api/pleroma/healthcheck")
|> json_response(200)
assert %{
"active" => _,
"healthy" => true,
"idle" => _,
"memory_used" => _,
"pool_size" => _
} = response
end
end
test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do
Pleroma.Config.put([:instance, :healthcheck], true)
with_mock Pleroma.Healthcheck,
system_info: fn -> %Pleroma.Healthcheck{healthy: false} end do
response =
conn
|> get("/api/pleroma/healthcheck")
|> json_response(503)
assert %{
"active" => _,
"healthy" => false,
"idle" => _,
"memory_used" => _,
"pool_size" => _
} = response
end
end
end end
describe "POST /api/pleroma/disable_account" do describe "POST /api/pleroma/disable_account" do