Merge branch 'develop' into 'fix/admin-api-user-deletion'

# Conflicts:
#   CHANGELOG.md
This commit is contained in:
Maxim Filippov 2019-08-14 22:45:32 +00:00
commit ec969eec51
93 changed files with 1262 additions and 416 deletions

View file

@ -23,9 +23,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Not being able to pin unlisted posts - Not being able to pin unlisted posts
- Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised. - Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised.
- Metadata rendering errors resulting in the entire page being inaccessible - Metadata rendering errors resulting in the entire page being inaccessible
- `federation_incoming_replies_max_depth` option being ignored in certain cases
- Federation/MediaProxy not working with instances that have wrong certificate order - Federation/MediaProxy not working with instances that have wrong certificate order
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
- 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 tread was muted
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes - Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
@ -34,16 +37,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Rich Media: The crawled URL is now spliced into the rich media data. - Rich Media: The crawled URL is now spliced into the rich media data.
- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification. - ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
- ActivityPub S2S: remote user deletions now work the same as local user deletions. - ActivityPub S2S: remote user deletions now work the same as local user deletions.
- ActivityPub S2S: POST requests are now signed with `(request-target)` pseudo-header.
- Not being able to access the Mastodon FE login page on private instances - Not being able to access the Mastodon FE login page on private instances
- 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
- Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected. - Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected.
- Report email not being sent to admins when the reporter is a remote user - Report email not being sent to admins when the reporter is a remote user
- MRF: ensure that subdomain_match calls are case-insensitive
- MRF: fix use of unserializable keyword lists in describe() implementations
- ActivityPub: Deactivated user deletion - ActivityPub: Deactivated user deletion
### Added ### 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.
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
- MRF: Support for excluding specific domains from Transparency. - MRF: Support for excluding specific domains from Transparency.
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`) - MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
- MRF: Support for filtering posts based on ActivityStreams vocabulary (`Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`)
- MRF (Simple Policy): Support for wildcard domains. - MRF (Simple Policy): Support for wildcard domains.
- Support for wildcard domains in user domain blocks setting. - Support for wildcard domains in user domain blocks setting.
- Configuration: `quarantined_instances` support wildcard domains. - Configuration: `quarantined_instances` support wildcard domains.
@ -66,6 +75,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added synchronization of following/followers counters for external users - Added synchronization of following/followers counters for external users
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`. - Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options. - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
- Configuration: `user_bio_length` and `user_name_length` options.
- Addressable lists - Addressable lists
- Twitter API: added rate limit for `/api/account/password_reset` endpoint. - Twitter API: added rate limit for `/api/account/password_reset` endpoint.
- ActivityPub: Add an internal service actor for fetching ActivityPub objects. - ActivityPub: Add an internal service actor for fetching ActivityPub objects.
@ -73,6 +83,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Endpoint for fetching latest user's statuses - Admin API: Endpoint for fetching latest user's statuses
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation. - Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
- Relays: Added a task to list relay subscriptions. - Relays: Added a task to list relay subscriptions.
- Mix Tasks: `mix pleroma.database fix_likes_collections`
- Federation: Remove `likes` from objects.
### Changed ### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
@ -83,6 +95,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Removed ### Removed
- Emoji: Remove longfox emojis. - Emoji: Remove longfox emojis.
- Remove `Reply-To` header from report emails for admins. - Remove `Reply-To` header from report emails for admins.
- ActivityPub: The `accept_blocks` configuration setting.
## [1.0.1] - 2019-07-14 ## [1.0.1] - 2019-07-14
### Security ### Security

View file

@ -253,6 +253,8 @@
skip_thread_containment: true, skip_thread_containment: true,
limit_to_local_content: :unauthenticated, limit_to_local_content: :unauthenticated,
dynamic_configuration: false, dynamic_configuration: false,
user_bio_length: 5000,
user_name_length: 100,
external_user_synchronization: true external_user_synchronization: true
config :pleroma, :markup, config :pleroma, :markup,
@ -302,7 +304,6 @@
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,
@ -337,6 +338,10 @@
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

@ -18,6 +18,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
## Pleroma.Uploaders.S3 ## Pleroma.Uploaders.S3
* `bucket`: S3 bucket name * `bucket`: S3 bucket name
* `bucket_namespace`: S3 bucket namespace
* `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com") * `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com")
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc. * `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
For example, when using CDN to S3 virtual host format, set "". For example, when using CDN to S3 virtual host format, set "".
@ -102,6 +103,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `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.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed. * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section) * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
* `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``
@ -125,6 +127,8 @@ config :pleroma, Pleroma.Emails.Mailer,
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`. * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`.
* `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``. * `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``.
* `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database. * `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database.
* `user_bio_length`: A user bio maximum length (default: `5000`)
* `user_name_length`: A user name maximum length (default: `100`)
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`. * `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
* `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`. * `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`.
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api. * `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
@ -275,6 +279,10 @@ config :pleroma, :mrf_subchain,
## :mrf_mention ## :mrf_mention
* `actors`: A list of actors, for which to drop any posts mentioning. * `actors`: A list of actors, for which to drop any posts mentioning.
## :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.
@ -328,7 +336,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

@ -36,6 +36,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, [], []} =
@ -125,4 +129,36 @@ def run(["prune_objects" | args]) 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

@ -224,6 +224,29 @@ def get_create_by_object_ap_id(ap_id) when is_binary(ap_id) do
def get_create_by_object_ap_id(_), do: nil def get_create_by_object_ap_id(_), do: nil
def create_by_object_ap_id_with_object(ap_ids) when is_list(ap_ids) do
from(
activity in Activity,
where:
fragment(
"coalesce((?)->'object'->>'id', (?)->>'object') = ANY(?)",
activity.data,
activity.data,
^ap_ids
),
where: fragment("(?)->>'type' = 'Create'", activity.data),
inner_join: o in Object,
on:
fragment(
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
o.data,
activity.data,
activity.data
),
preload: [object: o]
)
end
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
from( from(
activity in Activity, activity in Activity,
@ -263,8 +286,8 @@ defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}
defp get_in_reply_to_activity_from_object(_), do: nil defp get_in_reply_to_activity_from_object(_), do: nil
def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do def get_in_reply_to_activity(%Activity{} = activity) do
get_in_reply_to_activity_from_object(Object.normalize(object)) get_in_reply_to_activity_from_object(Object.normalize(activity))
end end
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])

View file

@ -3,11 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do defmodule Pleroma.Application do
import Cachex.Spec
use Application use Application
@name Mix.Project.config()[:name] @name Mix.Project.config()[:name]
@version Mix.Project.config()[:version] @version Mix.Project.config()[:version]
@repository Mix.Project.config()[:source_url] @repository Mix.Project.config()[:source_url]
@env Mix.env()
def name, do: @name def name, do: @name
def version, do: @version def version, do: @version
def named_version, do: @name <> " " <> @version def named_version, do: @name <> " " <> @version
@ -21,116 +24,25 @@ def user_agent do
# See http://elixir-lang.org/docs/stable/elixir/Application.html # See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications # for more information on OTP Applications
def start(_type, _args) do def start(_type, _args) do
import Cachex.Spec
Pleroma.Config.DeprecationWarnings.warn() Pleroma.Config.DeprecationWarnings.warn()
setup_instrumenters() setup_instrumenters()
# Define workers and child supervisors to be supervised # Define workers and child supervisors to be supervised
children = children =
[ [
# Start the Ecto repository Pleroma.Repo,
%{id: Pleroma.Repo, start: {Pleroma.Repo, :start_link, []}, type: :supervisor}, Pleroma.Config.TransferTask,
%{id: Pleroma.Config.TransferTask, start: {Pleroma.Config.TransferTask, :start_link, []}}, Pleroma.Emoji,
%{id: Pleroma.Emoji, start: {Pleroma.Emoji, :start_link, []}}, Pleroma.Captcha,
%{id: Pleroma.Captcha, start: {Pleroma.Captcha, :start_link, []}}, Pleroma.FlakeId,
%{ Pleroma.ScheduledActivityWorker
id: :cachex_used_captcha_cache,
start:
{Cachex, :start_link,
[
:used_captcha_cache,
[
ttl_interval:
:timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
]
]}
},
%{
id: :cachex_user,
start:
{Cachex, :start_link,
[
:user_cache,
[
default_ttl: 25_000,
ttl_interval: 1000,
limit: 2500
]
]}
},
%{
id: :cachex_object,
start:
{Cachex, :start_link,
[
:object_cache,
[
default_ttl: 25_000,
ttl_interval: 1000,
limit: 2500
]
]}
},
%{
id: :cachex_rich_media,
start:
{Cachex, :start_link,
[
:rich_media_cache,
[
default_ttl: :timer.minutes(120),
limit: 5000
]
]}
},
%{
id: :cachex_scrubber,
start:
{Cachex, :start_link,
[
:scrubber_cache,
[
limit: 2500
]
]}
},
%{
id: :cachex_idem,
start:
{Cachex, :start_link,
[
:idempotency_cache,
[
expiration:
expiration(
default: :timer.seconds(6 * 60 * 60),
interval: :timer.seconds(60)
),
limit: 2500
]
]}
},
%{id: Pleroma.FlakeId, start: {Pleroma.FlakeId, :start_link, []}},
%{
id: Pleroma.ScheduledActivityWorker,
start: {Pleroma.ScheduledActivityWorker, :start_link, []}
}
] ++ ] ++
cachex_children() ++
hackney_pool_children() ++ hackney_pool_children() ++
[ [
%{ Pleroma.Web.Federator.RetryQueue,
id: Pleroma.Web.Federator.RetryQueue, Pleroma.Web.OAuth.Token.CleanWorker,
start: {Pleroma.Web.Federator.RetryQueue, :start_link, []} Pleroma.Stats,
},
%{
id: Pleroma.Web.OAuth.Token.CleanWorker,
start: {Pleroma.Web.OAuth.Token.CleanWorker, :start_link, []}
},
%{
id: Pleroma.Stats,
start: {Pleroma.Stats, :start_link, []}
},
%{ %{
id: :web_push_init, id: :web_push_init,
start: {Task, :start_link, [&Pleroma.Web.Push.init/0]}, start: {Task, :start_link, [&Pleroma.Web.Push.init/0]},
@ -147,16 +59,12 @@ def start(_type, _args) do
restart: :temporary restart: :temporary
} }
] ++ ] ++
streamer_child() ++ oauth_cleanup_child(oauth_cleanup_enabled?()) ++
chat_child() ++ streamer_child(@env) ++
chat_child(@env, chat_enabled?()) ++
[ [
# Start the endpoint when the application starts Pleroma.Web.Endpoint,
%{ Pleroma.Gopher.Server
id: Pleroma.Web.Endpoint,
start: {Pleroma.Web.Endpoint, :start_link, []},
type: :supervisor
},
%{id: Pleroma.Gopher.Server, start: {Pleroma.Gopher.Server, :start_link, []}}
] ]
# See http://elixir-lang.org/docs/stable/elixir/Supervisor.html # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
@ -201,28 +109,54 @@ def enabled_hackney_pools do
end end
end end
if Pleroma.Config.get(:env) == :test do defp cachex_children do
defp streamer_child, do: [] [
defp chat_child, do: [] build_cachex("used_captcha", ttl_interval: seconds_valid_interval()),
else build_cachex("user", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
defp streamer_child do build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),
[%{id: Pleroma.Web.Streamer, start: {Pleroma.Web.Streamer, :start_link, []}}] build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),
build_cachex("scrubber", limit: 2500),
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500)
]
end end
defp chat_child do defp idempotency_expiration,
if Pleroma.Config.get([:chat, :enabled]) do do: expiration(default: :timer.seconds(6 * 60 * 60), interval: :timer.seconds(60))
[
%{ defp seconds_valid_interval,
id: Pleroma.Web.ChatChannel.ChatChannelState, do: :timer.seconds(Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]))
start: {Pleroma.Web.ChatChannel.ChatChannelState, :start_link, []}
defp build_cachex(type, opts),
do: %{
id: String.to_atom("cachex_" <> type),
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},
type: :worker
} }
]
else defp chat_enabled?, do: Pleroma.Config.get([:chat, :enabled])
[]
end defp oauth_cleanup_enabled?,
do: Pleroma.Config.get([:oauth2, :clean_expired_tokens], false)
defp streamer_child(:test), do: []
defp streamer_child(_) do
[Pleroma.Web.Streamer]
end end
defp oauth_cleanup_child(true),
do: [Pleroma.Web.OAuth.Token.CleanWorker]
defp oauth_cleanup_child(_), do: []
defp chat_child(:test, _), do: []
defp chat_child(_env, true) do
[Pleroma.Web.ChatChannel.ChatChannelState]
end end
defp chat_child(_, _), do: []
defp hackney_pool_children do defp hackney_pool_children do
for pool <- enabled_hackney_pools() do for pool <- enabled_hackney_pools() do
options = Pleroma.Config.get([:hackney_pools, pool]) options = Pleroma.Config.get([:hackney_pools, pool])

View file

@ -12,7 +12,7 @@ defmodule Pleroma.Captcha do
use GenServer use GenServer
@doc false @doc false
def start_link do def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__) GenServer.start_link(__MODULE__, [], name: __MODULE__)
end end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Config.TransferTask do
use Task use Task
alias Pleroma.Web.AdminAPI.Config alias Pleroma.Web.AdminAPI.Config
def start_link do def start_link(_) do
load_and_update_env() load_and_update_env()
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo) if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo)
:ignore :ignore

View file

@ -24,7 +24,7 @@ defmodule Pleroma.Emoji do
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}] @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
@doc false @doc false
def start_link do def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__) GenServer.start_link(__MODULE__, [], name: __MODULE__)
end end

View file

@ -98,7 +98,7 @@ def dump(value) do
def autogenerate, do: get() def autogenerate, do: get()
# -- GenServer API # -- GenServer API
def start_link do def start_link(_) do
:gen_server.start_link({:local, :flake}, __MODULE__, [], []) :gen_server.start_link({:local, :flake}, __MODULE__, [], [])
end end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Gopher.Server do
use GenServer use GenServer
require Logger require Logger
def start_link do def start_link(_) do
config = Pleroma.Config.get(:gopher, []) config = Pleroma.Config.get(:gopher, [])
ip = Keyword.get(config, :ip, {0, 0, 0, 0}) ip = Keyword.get(config, :ip, {0, 0, 0, 0})
port = Keyword.get(config, :port, 1234) port = Keyword.get(config, :port, 1234)

View file

@ -16,7 +16,7 @@ defmodule Pleroma.ScheduledActivityWorker do
@schedule_interval :timer.minutes(1) @schedule_interval :timer.minutes(1)
def start_link do def start_link(_) do
GenServer.start_link(__MODULE__, nil) GenServer.start_link(__MODULE__, nil)
end end

View file

@ -7,31 +7,56 @@ defmodule Pleroma.Stats do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
def start_link do use GenServer
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
spawn(fn -> schedule_update() end) @interval 1000 * 60 * 60
agent
def start_link(_) do
GenServer.start_link(__MODULE__, initial_data(), name: __MODULE__)
end
def force_update do
GenServer.call(__MODULE__, :force_update)
end end
def get_stats do def get_stats do
Agent.get(__MODULE__, fn {_, stats} -> stats end) %{stats: stats} = GenServer.call(__MODULE__, :get_state)
stats
end end
def get_peers do def get_peers do
Agent.get(__MODULE__, fn {peers, _} -> peers end) %{peers: peers} = GenServer.call(__MODULE__, :get_state)
peers
end end
def schedule_update do def init(args) do
spawn(fn -> Process.send(self(), :run_update, [])
# 1 hour {:ok, args}
Process.sleep(1000 * 60 * 60)
schedule_update()
end)
update_stats()
end end
def update_stats do def handle_call(:force_update, _from, _state) do
new_stats = get_stat_data()
{:reply, new_stats, new_stats}
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
def handle_info(:run_update, _state) do
new_stats = get_stat_data()
Process.send_after(self(), :run_update, @interval)
{:noreply, new_stats}
end
defp initial_data do
%{peers: [], stats: %{}}
end
defp get_stat_data do
peers = peers =
from( from(
u in User, u in User,
@ -52,8 +77,9 @@ def update_stats do
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id) user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
Agent.update(__MODULE__, fn _ -> %{
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}} peers: peers,
end) stats: %{domain_count: domain_count, status_count: status_count, user_count: user_count}
}
end end
end end

View file

@ -11,7 +11,7 @@ def get_file(_) do
def put_file(upload) do def put_file(upload) do
{local_path, file} = {local_path, file} =
case Enum.reverse(String.split(upload.path, "/", trim: true)) do case Enum.reverse(Path.split(upload.path)) do
[file] -> [file] ->
{upload_path(), file} {upload_path(), file}
@ -23,7 +23,7 @@ def put_file(upload) do
result_file = Path.join(local_path, file) result_file = Path.join(local_path, file)
unless File.exists?(result_file) do if not File.exists?(result_file) do
File.cp!(upload.tempfile, result_file) File.cp!(upload.tempfile, result_file)
end end

View file

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDII do defmodule Pleroma.Uploaders.MDII do
@moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.HTTP alias Pleroma.HTTP

View file

@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do
@behaviour Pleroma.Uploaders.Uploader @behaviour Pleroma.Uploaders.Uploader
require Logger require Logger
alias Pleroma.Config
# The file name is re-encoded with S3's constraints here to comply with previous # The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames # links with less strict filenames
def get_file(file) do def get_file(file) do
config = Pleroma.Config.get([__MODULE__]) config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket) bucket = Keyword.fetch!(config, :bucket)
bucket_with_namespace = bucket_with_namespace =
@ -34,15 +36,15 @@ def get_file(file) do
end end
def put_file(%Pleroma.Upload{} = upload) do def put_file(%Pleroma.Upload{} = upload) do
config = Pleroma.Config.get([__MODULE__]) config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket) bucket = Keyword.get(config, :bucket)
{:ok, file_data} = File.read(upload.tempfile)
s3_name = strict_encode(upload.path) s3_name = strict_encode(upload.path)
op = op =
ExAws.S3.put_object(bucket, s3_name, file_data, [ upload.tempfile
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(bucket, s3_name, [
{:acl, :public_read}, {:acl, :public_read},
{:content_type, upload.content_type} {:content_type, upload.content_type}
]) ])

View file

@ -152,10 +152,10 @@ def following_count(%User{} = user) do
end end
def remote_user_creation(params) do def remote_user_creation(params) do
params = bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
params name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|> Map.put(:info, params[:info] || %{})
params = Map.put(params, :info, params[:info] || %{})
info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info]) info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info])
changes = changes =
@ -164,8 +164,8 @@ def remote_user_creation(params) do
|> validate_required([:name, :ap_id]) |> validate_required([:name, :ap_id])
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_format(:nickname, @email_regex) |> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: 5000) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: 100) |> validate_length(:name, max: name_limit)
|> put_change(:local, false) |> put_change(:local, false)
|> put_embed(:info, info_cng) |> put_embed(:info, info_cng)
@ -188,22 +188,23 @@ def remote_user_creation(params) do
end end
def update_changeset(struct, params \\ %{}) do def update_changeset(struct, params \\ %{}) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
struct struct
|> cast(params, [:bio, :name, :avatar, :following]) |> cast(params, [:bio, :name, :avatar, :following])
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: 100) |> validate_length(:name, min: 1, max: name_limit)
end end
def upgrade_changeset(struct, params \\ %{}) do def upgrade_changeset(struct, params \\ %{}) do
params = bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
params name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|> Map.put(:last_refreshed_at, NaiveDateTime.utc_now())
info_cng = params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
struct.info info_cng = User.Info.user_upgrade(struct.info, params[:info])
|> User.Info.user_upgrade(params[:info])
struct struct
|> cast(params, [ |> cast(params, [
@ -216,8 +217,8 @@ def upgrade_changeset(struct, params \\ %{}) do
]) ])
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: 5000) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: 100) |> validate_length(:name, max: name_limit)
|> put_embed(:info, info_cng) |> put_embed(:info, info_cng)
end end
@ -244,6 +245,9 @@ def reset_password(%User{id: user_id} = user, data) do
end end
def register_changeset(struct, params \\ %{}, opts \\ []) do def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
need_confirmation? = need_confirmation? =
if is_nil(opts[:need_confirmation]) do if is_nil(opts[:need_confirmation]) do
Pleroma.Config.get([:instance, :account_activation_required]) Pleroma.Config.get([:instance, :account_activation_required])
@ -264,8 +268,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames])) |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex) |> validate_format(:email, @email_regex)
|> validate_length(:bio, max: 1000) |> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: 100) |> validate_length(:name, min: 1, max: name_limit)
|> put_change(:info, info_change) |> put_change(:info, info_change)
changeset = changeset =

View file

@ -520,6 +520,8 @@ defp fetch_activities_for_context_query(context, opts) do
from(activity in Activity) from(activity in Activity)
|> maybe_preload_objects(opts) |> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts) |> restrict_blocked(opts)
|> restrict_recipients(recipients, opts["user"]) |> restrict_recipients(recipients, opts["user"])
|> where( |> where(
@ -533,6 +535,7 @@ defp fetch_activities_for_context_query(context, opts) do
) )
) )
|> exclude_poll_votes(opts) |> exclude_poll_votes(opts)
|> exclude_id(opts)
|> order_by([activity], desc: activity.id) |> order_by([activity], desc: activity.id)
end end
@ -625,6 +628,7 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
params = params =
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("user", reading_user)
|> Map.put("actor_id", user.ap_id) |> Map.put("actor_id", user.ap_id)
|> Map.put("whole_db", true) |> Map.put("whole_db", true)
|> Map.put("pinned_activity_ids", user.info.pinned_activities) |> Map.put("pinned_activity_ids", user.info.pinned_activities)
@ -872,6 +876,12 @@ defp exclude_poll_votes(query, _) do
end end
end end
defp exclude_id(query, %{"exclude_id" => id}) when is_binary(id) do
from(activity in query, where: activity.id != ^id)
end
defp exclude_id(query, _), do: query
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
defp maybe_preload_objects(query, _) do defp maybe_preload_objects(query, _) do

View file

@ -28,11 +28,43 @@ defp get_policies(_), do: []
@spec subdomains_regex([String.t()]) :: [Regex.t()] @spec subdomains_regex([String.t()]) :: [Regex.t()]
def subdomains_regex(domains) when is_list(domains) do def subdomains_regex(domains) when is_list(domains) do
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$) for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i
end end
@spec subdomain_match?([Regex.t()], String.t()) :: boolean() @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
def subdomain_match?(domains, host) do def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end) Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
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 @@ def filter(%{"type" => "Follow", "actor" => actor_id} = message) 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 @@ defp contains_links?(%{"content" => content} = _object) 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 @@ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message
# 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 @@ def filter(object) 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

@ -39,4 +39,6 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
end end
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
def describe, do: {:ok, %{}}
end end

View file

@ -90,4 +90,8 @@ def filter(%{"type" => "Create"} = message) 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) |> Enum.into(%{})}}
end end

View file

@ -96,4 +96,36 @@ def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message
@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

@ -53,4 +53,7 @@ def filter(
@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

@ -21,4 +21,7 @@ def filter(%{"type" => "Create"} = message) 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

@ -19,4 +19,7 @@ def filter(
@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

@ -21,4 +21,6 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
end end
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
def describe, do: {:ok, %{}}
end end

View file

@ -44,4 +44,8 @@ def filter(%{"type" => "Create"} = object) 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) |> Enum.into(%{})}}
end end

View file

@ -177,4 +177,16 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
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 @@ def filter(%{"actor" => actor} = message) 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

@ -165,4 +165,7 @@ def filter(%{"actor" => actor, "type" => "Create"} = message),
@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

@ -32,4 +32,13 @@ def filter(%{"actor" => actor} = object) 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,37 @@
# 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) |> Enum.into(%{})}}
end

View file

@ -46,7 +46,7 @@ def is_representable?(%Activity{} = activity) do
""" """
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}") Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host %{host: host, path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@ -56,6 +56,7 @@ def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = pa
signature = signature =
Pleroma.Signature.sign(actor, %{ Pleroma.Signature.sign(actor, %{
"(request-target)": "post #{path}",
host: host, host: host,
"content-length": byte_size(json), "content-length": byte_size(json),
digest: digest, digest: digest,

View file

@ -14,6 +14,7 @@ def get_actor do
|> User.get_or_create_service_actor_by_ap_id() |> User.get_or_create_service_actor_by_ap_id()
end end
@spec follow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def follow(target_instance) do def follow(target_instance) do
with %User{} = local_user <- get_actor(), with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance), {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
@ -21,12 +22,17 @@ def follow(target_instance) do
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}") Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity} {:ok, activity}
else else
{:error, _} = error ->
Logger.error("error: #{inspect(error)}")
error
e -> e ->
Logger.error("error: #{inspect(e)}") Logger.error("error: #{inspect(e)}")
{:error, e} {:error, e}
end end
end end
@spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
def unfollow(target_instance) do def unfollow(target_instance) do
with %User{} = local_user <- get_actor(), with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance), {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
@ -34,20 +40,27 @@ def unfollow(target_instance) do
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}") Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity} {:ok, activity}
else else
{:error, _} = error ->
Logger.error("error: #{inspect(error)}")
error
e -> e ->
Logger.error("error: #{inspect(e)}") Logger.error("error: #{inspect(e)}")
{:error, e} {:error, e}
end end
end end
@spec publish(any()) :: {:ok, Activity.t(), Object.t()} | {:error, any()}
def publish(%Activity{data: %{"type" => "Create"}} = activity) do def publish(%Activity{data: %{"type" => "Create"}} = activity) do
with %User{} = user <- get_actor(), with %User{} = user <- get_actor(),
%Object{} = object <- Object.normalize(activity) do %Object{} = object <- Object.normalize(activity) do
ActivityPub.announce(user, object, nil, true, false) ActivityPub.announce(user, object, nil, true, false)
else else
e -> Logger.error("error: #{inspect(e)}") e ->
Logger.error("error: #{inspect(e)}")
{:error, inspect(e)}
end end
end end
def publish(_), do: nil def publish(_), do: {:error, "Not implemented"}
end end

View file

@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
""" """
def fix_object(object, options \\ []) do def fix_object(object, options \\ []) do
object object
|> strip_internal_fields
|> fix_actor |> fix_actor
|> fix_url |> fix_url
|> fix_attachments |> fix_attachments
@ -34,7 +35,6 @@ def fix_object(object, options \\ []) do
|> 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(options) |> fix_type(options)
@ -151,20 +151,6 @@ def fix_actor(%{"attributedTo" => actor} = object) do
|> Map.put("actor", Containment.get_actor(%{"actor" => actor})) |> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
end end
# Check for standardisation
# 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
object
end
def fix_in_reply_to(object, options \\ []) def fix_in_reply_to(object, options \\ [])
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
@ -347,13 +333,15 @@ def fix_content_map(object), do: object
def fix_type(object, options \\ []) def fix_type(object, options \\ [])
def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do
reply = reply =
if Federator.allowed_incoming_reply_depth?(options[:depth]) do with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
Object.normalize(reply_id, true) {:ok, object} <- get_obj_helper(reply_id, options) do
object
end end
if reply && (reply.data["type"] == "Question" and object["name"]) do if reply && reply.data["type"] == "Question" do
Map.put(object, "type", "Answer") Map.put(object, "type", "Answer")
else else
object object
@ -713,8 +701,7 @@ def handle_incoming(
} = _data, } = _data,
_options _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)
@ -728,8 +715,7 @@ def handle_incoming(
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data, %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
_options _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)
@ -784,7 +770,6 @@ def prepare_object(object) 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
@ -971,22 +956,6 @@ def add_attributed_to(object) 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"] || [])
@ -1002,6 +971,7 @@ def prepare_attachments(object) 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

@ -33,9 +33,11 @@ def handle_in("new_msg", %{"text" => text}, %{assigns: %{user_name: user_name}}
end end
defmodule Pleroma.Web.ChatChannel.ChatChannelState do defmodule Pleroma.Web.ChatChannel.ChatChannelState do
use Agent
@max_messages 20 @max_messages 20
def start_link do def start_link(_) do
Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__) Agent.start_link(fn -> %{max_id: 1, messages: []} end, name: __MODULE__)
end end

View file

@ -13,7 +13,7 @@ def init(args) do
{:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}} {:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}}
end end
def start_link do def start_link(_) do
enabled = enabled =
if Pleroma.Config.get(:env) == :test, if Pleroma.Config.get(:env) == :test,
do: true, do: true,

View file

@ -13,10 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
@spec follow(User.t(), User.t(), map) :: {:ok, User.t()} | {:error, String.t()}
def follow(follower, followed, params \\ %{}) do def follow(follower, followed, params \\ %{}) do
options = cast_params(params)
reblogs = options[:reblogs]
result = result =
if not User.following?(follower, followed) do if not User.following?(follower, followed) do
CommonAPI.follow(follower, followed) CommonAPI.follow(follower, followed)
@ -24,19 +22,25 @@ def follow(follower, followed, params \\ %{}) do
{:ok, follower, followed, nil} {:ok, follower, followed, nil}
end end
with {:ok, follower, followed, _} <- result do with {:ok, follower, _followed, _} <- result do
reblogs options = cast_params(params)
|> case do
false -> CommonAPI.hide_reblogs(follower, followed) case reblogs_visibility(options[:reblogs], result) do
_ -> CommonAPI.show_reblogs(follower, followed)
end
|> case do
{:ok, follower} -> {:ok, follower} {:ok, follower} -> {:ok, follower}
_ -> {:ok, follower} _ -> {:ok, follower}
end end
end end
end end
defp reblogs_visibility(false, {:ok, follower, followed, _}) do
CommonAPI.hide_reblogs(follower, followed)
end
defp reblogs_visibility(_, {:ok, follower, followed, _}) do
CommonAPI.show_reblogs(follower, followed)
end
@spec get_followers(User.t(), map()) :: list(User.t())
def get_followers(user, params \\ %{}) do def get_followers(user, params \\ %{}) do
user user
|> User.get_followers_query() |> User.get_followers_query()

View file

@ -435,6 +435,7 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("local_only", local_only) |> Map.put("local_only", local_only)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user) |> Map.put("muting_user", user)
|> Map.put("user", user)
|> ActivityPub.fetch_public_activities() |> ActivityPub.fetch_public_activities()
|> Enum.reverse() |> Enum.reverse()
@ -496,12 +497,9 @@ def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
activities <- activities <-
ActivityPub.fetch_activities_for_context(activity.data["context"], %{ ActivityPub.fetch_activities_for_context(activity.data["context"], %{
"blocking_user" => user, "blocking_user" => user,
"user" => user "user" => user,
"exclude_id" => activity.id
}), }),
activities <-
activities |> Enum.filter(fn %{id: aid} -> to_string(aid) != to_string(id) end),
activities <-
activities |> Enum.filter(fn %{data: %{"type" => type}} -> type == "Create" end),
grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do
result = %{ result = %{
ancestors: ancestors:
@ -536,8 +534,8 @@ def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|> put_view(StatusView) |> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user}) |> try_render("poll.json", %{object: object, for: user})
else else
nil -> render_error(conn, :not_found, "Record not found") error when is_nil(error) or error == false ->
false -> render_error(conn, :not_found, "Record not found") render_error(conn, :not_found, "Record not found")
end end
end end
@ -885,8 +883,8 @@ def get_mascot(%{assigns: %{user: user}} = conn, _params) do
end end
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^likes) q = from(u in User, where: u.ap_id in ^likes)
users = users =
@ -902,8 +900,8 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^announces) q = from(u in User, where: u.ap_id in ^announces)
users = users =
@ -944,6 +942,7 @@ def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("local_only", local_only) |> Map.put("local_only", local_only)
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("muting_user", user) |> Map.put("muting_user", user)
|> Map.put("user", user)
|> Map.put("tag", tags) |> Map.put("tag", tags)
|> Map.put("tag_all", tag_all) |> Map.put("tag_all", tag_all)
|> Map.put("tag_reject", tag_reject) |> Map.put("tag_reject", tag_reject)
@ -1350,6 +1349,7 @@ def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params)
params params
|> Map.put("type", "Create") |> Map.put("type", "Create")
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("user", user)
|> Map.put("muting_user", user) |> Map.put("muting_user", user)
# we must filter the following list for the user to avoid leaking statuses the user # we must filter the following list for the user to avoid leaking statuses the user
@ -1690,45 +1690,35 @@ def suggestions(%{assigns: %{user: user}} = conn, _) do
|> String.replace("{{user}}", user) |> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
HTTP.get( HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
url,
[],
adapter: [
recv_timeout: timeout,
pool: :default
]
),
{:ok, data} <- Jason.decode(body) do {:ok, data} <- Jason.decode(body) do
data = data =
data data
|> Enum.slice(0, limit) |> Enum.slice(0, limit)
|> Enum.map(fn x -> |> Enum.map(fn x ->
Map.put( x
x, |> Map.put("id", fetch_suggestion_id(x))
"id", |> Map.put("avatar", MediaProxy.url(x["avatar"]))
case User.get_or_fetch(x["acct"]) do |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
{:ok, %User{id: id}} -> id
_ -> 0
end
)
end)
|> Enum.map(fn x ->
Map.put(x, "avatar", MediaProxy.url(x["avatar"]))
end)
|> Enum.map(fn x ->
Map.put(x, "avatar_static", MediaProxy.url(x["avatar_static"]))
end) end)
conn json(conn, data)
|> json(data)
else else
e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") e ->
Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
end end
else else
json(conn, []) json(conn, [])
end end
end end
defp fetch_suggestion_id(attrs) do
case User.get_or_fetch(attrs["acct"]) do
{:ok, %User{id: id}} -> id
_ -> 0
end
end
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
with %Activity{} = activity <- Activity.get_by_id(status_id), with %Activity{} = activity <- Activity.get_by_id(status_id),
true <- Visibility.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do

View file

@ -72,6 +72,13 @@ defp do_render("account.json", %{user: user} = opts) 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 =
@ -102,8 +109,8 @@ defp do_render("account.json", %{user: user} = opts) 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.profile_url(user), url: User.profile_url(user),

View file

@ -5,6 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.StatusView do defmodule Pleroma.Web.MastodonAPI.StatusView do
use Pleroma.Web, :view use Pleroma.Web, :view
require Pleroma.Constants
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Object alias Pleroma.Object
@ -24,19 +26,19 @@ defp get_replied_to_activities([]), do: %{}
defp get_replied_to_activities(activities) do defp get_replied_to_activities(activities) do
activities activities
|> Enum.map(fn |> Enum.map(fn
%{data: %{"type" => "Create", "object" => object}} -> %{data: %{"type" => "Create"}} = activity ->
object = Object.normalize(object) object = Object.normalize(activity)
object.data["inReplyTo"] != "" && object.data["inReplyTo"] object && object.data["inReplyTo"] != "" && object.data["inReplyTo"]
_ -> _ ->
nil nil
end) end)
|> Enum.filter(& &1) |> Enum.filter(& &1)
|> Activity.create_by_object_ap_id() |> Activity.create_by_object_ap_id_with_object()
|> Repo.all() |> Repo.all()
|> Enum.reduce(%{}, fn activity, acc -> |> Enum.reduce(%{}, fn activity, acc ->
object = Object.normalize(activity) object = Object.normalize(activity)
Map.put(acc, object.data["id"], activity) if object, do: Map.put(acc, object.data["id"], activity), else: acc
end) end)
end end
@ -88,6 +90,7 @@ def render(
reblogged_activity = reblogged_activity =
Activity.create_by_object_ap_id(activity_object.data["id"]) Activity.create_by_object_ap_id(activity_object.data["id"])
|> Activity.with_preloaded_bookmark(opts[:for]) |> Activity.with_preloaded_bookmark(opts[:for])
|> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.one() |> Repo.one()
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity)) reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
@ -142,6 +145,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
object = Object.normalize(activity) object = Object.normalize(activity)
user = get_user(activity.data["actor"]) user = get_user(activity.data["actor"])
user_follower_address = user.follower_address
like_count = object.data["like_count"] || 0 like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0 announcement_count = object.data["announcement_count"] || 0
@ -157,7 +161,11 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
mentions = mentions =
(object.data["to"] ++ tag_mentions) (object.data["to"] ++ tag_mentions)
|> Enum.uniq() |> Enum.uniq()
|> Enum.map(fn ap_id -> User.get_cached_by_ap_id(ap_id) end) |> Enum.map(fn
Pleroma.Constants.as_public() -> nil
^user_follower_address -> nil
ap_id -> User.get_cached_by_ap_id(ap_id)
end)
|> Enum.filter(& &1) |> Enum.filter(& &1)
|> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end) |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end)
@ -168,7 +176,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity
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 @@ def schemas(conn, _params) 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

@ -6,36 +6,30 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do
@moduledoc """ @moduledoc """
The module represents functions to clean an expired oauth tokens. The module represents functions to clean an expired oauth tokens.
""" """
use GenServer
@ten_seconds 10_000
@one_day 86_400_000
# 10 seconds
@start_interval 10_000
@interval Pleroma.Config.get( @interval Pleroma.Config.get(
# 24 hours
[:oauth2, :clean_expired_tokens_interval], [:oauth2, :clean_expired_tokens_interval],
86_400_000 @one_day
) )
@queue :background
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
def start_link, do: GenServer.start_link(__MODULE__, nil) def start_link(_), do: GenServer.start_link(__MODULE__, %{})
def init(_) do def init(_) do
if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do Process.send_after(self(), :perform, @ten_seconds)
Process.send_after(self(), :perform, @start_interval)
{:ok, nil} {:ok, nil}
else
:ignore
end
end end
@doc false @doc false
def handle_info(:perform, state) do def handle_info(:perform, state) do
Token.delete_expired_tokens()
Process.send_after(self(), :perform, @interval) Process.send_after(self(), :perform, @interval)
PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean])
{:noreply, state} {:noreply, state}
end end
# Job Worker Callbacks
def perform(:clean), do: Token.delete_expired_tokens()
end end

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Web.Streamer do
@keepalive_interval :timer.seconds(30) @keepalive_interval :timer.seconds(30)
def start_link do def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__) GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end end
@ -35,28 +35,21 @@ def stream(topic, item) do
end end
def init(args) do def init(args) do
spawn(fn -> Process.send_after(self(), %{action: :ping}, @keepalive_interval)
# 30 seconds
Process.sleep(@keepalive_interval)
GenServer.cast(__MODULE__, %{action: :ping})
end)
{:ok, args} {:ok, args}
end end
def handle_cast(%{action: :ping}, topics) do def handle_info(%{action: :ping}, topics) do
Map.values(topics) topics
|> Map.values()
|> List.flatten() |> List.flatten()
|> Enum.each(fn socket -> |> Enum.each(fn socket ->
Logger.debug("Sending keepalive ping") Logger.debug("Sending keepalive ping")
send(socket.transport_pid, {:text, ""}) send(socket.transport_pid, {:text, ""})
end) end)
spawn(fn -> Process.send_after(self(), %{action: :ping}, @keepalive_interval)
# 30 seconds
Process.sleep(@keepalive_interval)
GenServer.cast(__MODULE__, %{action: :ping})
end)
{:noreply, topics} {:noreply, topics}
end end

View file

@ -58,10 +58,10 @@ def safe_render(view, template, assigns \\ %{}) do
rescue rescue
error -> error ->
Logger.error( Logger.error(
"#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}" "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
Exception.format(:error, error, __STACKTRACE__)
) )
Logger.error(inspect(__STACKTRACE__))
nil nil
end end

View file

@ -95,7 +95,7 @@ defp oauth_deps do
defp deps do defp deps do
[ [
{:phoenix, "~> 1.4.8"}, {:phoenix, "~> 1.4.8"},
{:tzdata, "~> 1.0"}, {:tzdata, "~> 0.5.21"},
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.0"},
@ -114,8 +114,9 @@ defp deps do
{:tesla, "~> 1.2"}, {:tesla, "~> 1.2"},
{:jason, "~> 1.0"}, {:jason, "~> 1.0"},
{:mogrify, "~> 0.6.1"}, {:mogrify, "~> 0.6.1"},
{:ex_aws, "~> 2.0"}, {:ex_aws, "~> 2.1"},
{:ex_aws_s3, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"},
{:earmark, "~> 1.3"}, {:earmark, "~> 1.3"},
{:bbcode, "~> 0.1.1"}, {:bbcode, "~> 0.1.1"},
{:ex_machina, "~> 2.3", only: :test}, {:ex_machina, "~> 2.3", only: :test},

View file

@ -80,13 +80,14 @@
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]}, "recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"},
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, "swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]}, "syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "tesla": {:hex, :tesla, "1.2.1", "864783cc27f71dd8c8969163704752476cec0f3a51eb3b06393b3971dc9733ff", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "1.0.1", "f6027a331af7d837471248e62733c6ebee86a72e57c613aa071ebb1f750fc71a", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.21", "8cbf3607fcce69636c672d5be2bbb08687fe26639a62bdcc283d267277db7cf0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "ueberauth": {:hex, :ueberauth, "0.6.1", "9e90d3337dddf38b1ca2753aca9b1e53d8a52b890191cdc55240247c89230412", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm"},

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.f74c256b.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.d8d12c12.js></script><script type=text/javascript src=static/js/chunk-elementUI.1fa5434b.js></script><script type=text/javascript src=static/js/chunk-libs.d5609760.js></script><script type=text/javascript src=static/js/app.4137ad8f.js></script></body></html> <!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.e5cd8da6.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.f40c8ec4.js></script><script type=text/javascript src=static/js/chunk-elementUI.1911151b.js></script><script type=text/javascript src=static/js/chunk-libs.fb0b7f4a.js></script><script type=text/javascript src=static/js/app.8e186193.js></script></body></html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -31,7 +31,7 @@ test "transfer config values from db to env" do
value: [live: 15, com: 35] value: [live: 15, com: 35]
}) })
Pleroma.Config.TransferTask.start_link() Pleroma.Config.TransferTask.start_link([])
assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3] assert Application.get_env(:pleroma, :test_key) == [live: 2, com: 3]
assert Application.get_env(:idna, :test_key) == [live: 15, com: 35] assert Application.get_env(:idna, :test_key) == [live: 15, com: 35]
@ -50,7 +50,7 @@ test "non existing atom" do
}) })
assert ExUnit.CaptureLog.capture_log(fn -> assert ExUnit.CaptureLog.capture_log(fn ->
Pleroma.Config.TransferTask.start_link() Pleroma.Config.TransferTask.start_link([])
end) =~ end) =~
"updating env causes error, key: \"undefined_atom_key\", error: %ArgumentError{message: \"argument error\"}" "updating env causes error, key: \"undefined_atom_key\", error: %ArgumentError{message: \"argument error\"}"
end end

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 @@ test "following and followers count are updated" 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

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.LocalTest do
use Pleroma.DataCase
alias Pleroma.Uploaders.Local
describe "get_file/1" do
test "it returns path to local folder for files" do
assert Local.get_file("") == {:ok, {:static_dir, "test/uploads"}}
end
end
describe "put_file/1" do
test "put file to local folder" do
file_path = "local_upload/files/image.jpg"
file = %Pleroma.Upload{
name: "image.jpg",
content_type: "image/jpg",
path: file_path,
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
assert Local.put_file(file) == :ok
assert Path.join([Local.upload_path(), file_path])
|> File.exists?()
end
end
end

View file

@ -0,0 +1,50 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.MDIITest do
use Pleroma.DataCase
alias Pleroma.Uploaders.MDII
import Tesla.Mock
describe "get_file/1" do
test "it returns path to local folder for files" do
assert MDII.get_file("") == {:ok, {:static_dir, "test/uploads"}}
end
end
describe "put_file/1" do
setup do
file_upload = %Pleroma.Upload{
name: "mdii-image.jpg",
content_type: "image/jpg",
path: "test_folder/mdii-image.jpg",
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
[file_upload: file_upload]
end
test "save file", %{file_upload: file_upload} do
mock(fn
%{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
%Tesla.Env{status: 200, body: "mdii-image"}
end)
assert MDII.put_file(file_upload) ==
{:ok, {:url, "https://mdii.sakura.ne.jp/mdii-image.jpg"}}
end
test "save file to local if MDII isn`t available", %{file_upload: file_upload} do
mock(fn
%{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} ->
%Tesla.Env{status: 500}
end)
assert MDII.put_file(file_upload) == :ok
assert Path.join([Pleroma.Uploaders.Local.upload_path(), file_upload.path])
|> File.exists?()
end
end
end

View file

@ -0,0 +1,90 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.S3Test do
use Pleroma.DataCase
alias Pleroma.Config
alias Pleroma.Uploaders.S3
import Mock
import ExUnit.CaptureLog
setup do
config = Config.get([Pleroma.Uploaders.S3])
Config.put([Pleroma.Uploaders.S3],
bucket: "test_bucket",
public_endpoint: "https://s3.amazonaws.com"
)
on_exit(fn ->
Config.put([Pleroma.Uploaders.S3], config)
end)
:ok
end
describe "get_file/1" do
test "it returns path to local folder for files" do
assert S3.get_file("test_image.jpg") == {
:ok,
{:url, "https://s3.amazonaws.com/test_bucket/test_image.jpg"}
}
end
test "it returns path without bucket when truncated_namespace set to ''" do
Config.put([Pleroma.Uploaders.S3],
bucket: "test_bucket",
public_endpoint: "https://s3.amazonaws.com",
truncated_namespace: ""
)
assert S3.get_file("test_image.jpg") == {
:ok,
{:url, "https://s3.amazonaws.com/test_image.jpg"}
}
end
test "it returns path with bucket namespace when namespace is set" do
Config.put([Pleroma.Uploaders.S3],
bucket: "test_bucket",
public_endpoint: "https://s3.amazonaws.com",
bucket_namespace: "family"
)
assert S3.get_file("test_image.jpg") == {
:ok,
{:url, "https://s3.amazonaws.com/family:test_bucket/test_image.jpg"}
}
end
end
describe "put_file/1" do
setup do
file_upload = %Pleroma.Upload{
name: "image-tet.jpg",
content_type: "image/jpg",
path: "test_folder/image-tet.jpg",
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
[file_upload: file_upload]
end
test "save file", %{file_upload: file_upload} do
with_mock ExAws, request: fn _ -> {:ok, :ok} end do
assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}}
end
end
test "returns error", %{file_upload: file_upload} do
with_mock ExAws, request: fn _ -> {:error, "S3 Upload failed"} end do
assert capture_log(fn ->
assert S3.put_file(file_upload) == {:error, "S3 Upload failed"}
end) =~ "Elixir.Pleroma.Uploaders.S3: {:error, \"S3 Upload failed\"}"
end
end
end
end

View file

@ -525,7 +525,10 @@ test "it has required fields" do
end end
test "it restricts some sizes" do test "it restricts some sizes" do
[bio: 5000, name: 100] bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
[bio: bio_limit, name: name_limit]
|> Enum.each(fn {field, size} -> |> Enum.each(fn {field, size} ->
string = String.pad_leading(".", size) string = String.pad_leading(".", size)
cs = User.remote_user_creation(Map.put(@valid_remote, field, string)) cs = User.remote_user_creation(Map.put(@valid_remote, field, string))

View file

@ -4,8 +4,8 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
test "subdomains_regex/1" do test "subdomains_regex/1" do
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
~r/^unsafe.tld$/, ~r/^unsafe.tld$/i,
~r/^(.*\.)*unsafe.tld$/ ~r/^(.*\.)*unsafe.tld$/i
] ]
end end
@ -13,7 +13,7 @@ test "subdomains_regex/1" do
test "common domains" do test "common domains" do
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/] assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]
assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "unsafe2.tld") assert MRF.subdomain_match?(regexes, "unsafe2.tld")
@ -24,7 +24,7 @@ test "common domains" do
test "wildcard domains with one subdomain" do test "wildcard domains with one subdomain" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"]) regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/] assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
@ -35,12 +35,52 @@ test "wildcard domains with one subdomain" do
test "wildcard domains with two subdomains" do test "wildcard domains with two subdomains" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"]) regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/] assert regexes == [~r/^(.*\.)*unsafe.tld$/i]
assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.sub.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.anotherunsafe.tld")
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother") refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
end 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
end end

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

@ -5,11 +5,71 @@
defmodule Pleroma.Web.ActivityPub.RelayTest do defmodule Pleroma.Web.ActivityPub.RelayTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
import Pleroma.Factory
test "gets an actor for the relay" do test "gets an actor for the relay" do
user = Relay.get_actor() user = Relay.get_actor()
assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay"
end
assert user.ap_id =~ "/relay" describe "follow/1" do
test "returns errors when user not found" do
assert Relay.follow("test-ap-id") == {:error, "Could not fetch by AP id"}
end
test "returns activity" do
user = insert(:user)
service_actor = Relay.get_actor()
assert {:ok, %Activity{} = activity} = Relay.follow(user.ap_id)
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
assert user.ap_id in activity.recipients
assert activity.data["type"] == "Follow"
assert activity.data["actor"] == service_actor.ap_id
assert activity.data["object"] == user.ap_id
end
end
describe "unfollow/1" do
test "returns errors when user not found" do
assert Relay.unfollow("test-ap-id") == {:error, "Could not fetch by AP id"}
end
test "returns activity" do
user = insert(:user)
service_actor = Relay.get_actor()
ActivityPub.follow(service_actor, user)
assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id)
assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
assert user.ap_id in activity.recipients
assert activity.data["type"] == "Undo"
assert activity.data["actor"] == service_actor.ap_id
assert activity.data["to"] == [user.ap_id]
end
end
describe "publish/1" do
test "returns error when activity not `Create` type" do
activity = insert(:like_activity)
assert Relay.publish(activity) == {:error, "Not implemented"}
end
test "returns error when activity not public" do
activity = insert(:direct_note_activity)
assert Relay.publish(activity) == {:error, false}
end
test "returns announce activity" do
service_actor = Relay.get_actor()
note = insert(:note_activity)
assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note)
assert activity.data["type"] == "Announce"
assert activity.data["actor"] == service_actor.ap_id
assert activity.data["object"] == obj.data["id"]
end
end end
end end

View file

@ -450,6 +450,27 @@ test "it ensures that address fields become lists" 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!()
@ -1061,14 +1082,7 @@ test "it strips internal fields of article" 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

@ -356,4 +356,31 @@ test "sanitizes display names" 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

@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -85,11 +86,11 @@ test "the public timeline", %{conn: conn} do
end end
test "the public timeline when public is set to false", %{conn: conn} do test "the public timeline when public is set to false", %{conn: conn} do
public = Pleroma.Config.get([:instance, :public]) public = Config.get([:instance, :public])
Pleroma.Config.put([:instance, :public], false) Config.put([:instance, :public], false)
on_exit(fn -> on_exit(fn ->
Pleroma.Config.put([:instance, :public], public) Config.put([:instance, :public], public)
end) end)
assert conn assert conn
@ -250,7 +251,7 @@ test "posting a fake status", %{conn: conn} do
end end
test "posting a status with OGP link preview", %{conn: conn} do test "posting a status with OGP link preview", %{conn: conn} do
Pleroma.Config.put([:rich_media, :enabled], true) Config.put([:rich_media, :enabled], true)
conn = conn =
conn conn
@ -260,7 +261,7 @@ test "posting a status with OGP link preview", %{conn: conn} do
assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
assert Activity.get_by_id(id) assert Activity.get_by_id(id)
Pleroma.Config.put([:rich_media, :enabled], false) Config.put([:rich_media, :enabled], false)
end end
test "posting a direct status", %{conn: conn} do test "posting a direct status", %{conn: conn} do
@ -304,7 +305,7 @@ test "posting a poll", %{conn: conn} do
test "option limit is enforced", %{conn: conn} do test "option limit is enforced", %{conn: conn} do
user = insert(:user) user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_options]) limit = Config.get([:instance, :poll_limits, :max_options])
conn = conn =
conn conn
@ -320,7 +321,7 @@ test "option limit is enforced", %{conn: conn} do
test "option character limit is enforced", %{conn: conn} do test "option character limit is enforced", %{conn: conn} do
user = insert(:user) user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars]) limit = Config.get([:instance, :poll_limits, :max_option_chars])
conn = conn =
conn conn
@ -339,7 +340,7 @@ test "option character limit is enforced", %{conn: conn} do
test "minimal date limit is enforced", %{conn: conn} do test "minimal date limit is enforced", %{conn: conn} do
user = insert(:user) user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration]) limit = Config.get([:instance, :poll_limits, :min_expiration])
conn = conn =
conn conn
@ -358,7 +359,7 @@ test "minimal date limit is enforced", %{conn: conn} do
test "maximum date limit is enforced", %{conn: conn} do test "maximum date limit is enforced", %{conn: conn} do
user = insert(:user) user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration]) limit = Config.get([:instance, :poll_limits, :max_expiration])
conn = conn =
conn conn
@ -1633,12 +1634,12 @@ test "returns the relationships for the current user", %{conn: conn} do
describe "media upload" do describe "media upload" do
setup do setup do
upload_config = Pleroma.Config.get([Pleroma.Upload]) upload_config = Config.get([Pleroma.Upload])
proxy_config = Pleroma.Config.get([:media_proxy]) proxy_config = Config.get([:media_proxy])
on_exit(fn -> on_exit(fn ->
Pleroma.Config.put([Pleroma.Upload], upload_config) Config.put([Pleroma.Upload], upload_config)
Pleroma.Config.put([:media_proxy], proxy_config) Config.put([:media_proxy], proxy_config)
end) end)
user = insert(:user) user = insert(:user)
@ -2581,7 +2582,7 @@ test "get instance information", %{conn: conn} do
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200) assert result = json_response(conn, 200)
email = Pleroma.Config.get([:instance, :email]) email = Config.get([:instance, :email])
# Note: not checking for "max_toot_chars" since it's optional # Note: not checking for "max_toot_chars" since it's optional
assert %{ assert %{
"uri" => _, "uri" => _,
@ -2623,7 +2624,7 @@ test "get instance stats", %{conn: conn} do
|> Changeset.put_embed(:info, info_change) |> Changeset.put_embed(:info, info_change)
|> User.update_and_set_cache() |> User.update_and_set_cache()
Pleroma.Stats.update_stats() Pleroma.Stats.force_update()
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
@ -2641,7 +2642,7 @@ test "get peers", %{conn: conn} do
insert(:user, %{local: false, nickname: "u@peer1.com"}) insert(:user, %{local: false, nickname: "u@peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"}) insert(:user, %{local: false, nickname: "u@peer2.com"})
Pleroma.Stats.update_stats() Pleroma.Stats.force_update()
conn = get(conn, "/api/v1/instance/peers") conn = get(conn, "/api/v1/instance/peers")
@ -2666,7 +2667,7 @@ test "put settings", %{conn: conn} do
describe "pinned statuses" do describe "pinned statuses" do
setup do setup do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1) Config.put([:instance, :max_pinned_statuses], 1)
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
@ -2766,10 +2767,10 @@ test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
describe "cards" do describe "cards" do
setup do setup do
Pleroma.Config.put([:rich_media, :enabled], true) Config.put([:rich_media, :enabled], true)
on_exit(fn -> on_exit(fn ->
Pleroma.Config.put([:rich_media, :enabled], false) Config.put([:rich_media, :enabled], false)
end) end)
user = insert(:user) user = insert(:user)
@ -2901,8 +2902,10 @@ test "bookmarks" 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
@ -2995,7 +2998,7 @@ test "comment must be up to the size specified in the config", %{
reporter: reporter, reporter: reporter,
target_user: target_user target_user: target_user
} do } do
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) max_size = Config.get([:instance, :max_report_comment_size], 1000)
comment = String.pad_trailing("a", max_size + 1, "a") comment = String.pad_trailing("a", max_size + 1, "a")
error = %{"error" => "Comment must be up to #{max_size} characters"} error = %{"error" => "Comment must be up to #{max_size} characters"}
@ -3124,15 +3127,15 @@ test "redirects not logged-in users to the login page on private instances", %{
conn: conn, conn: conn,
path: path path: path
} do } do
is_public = Pleroma.Config.get([:instance, :public]) is_public = Config.get([:instance, :public])
Pleroma.Config.put([:instance, :public], false) Config.put([:instance, :public], false)
conn = get(conn, path) conn = get(conn, path)
assert conn.status == 302 assert conn.status == 302
assert redirected_to(conn) == "/web/login" assert redirected_to(conn) == "/web/login"
Pleroma.Config.put([:instance, :public], is_public) Config.put([:instance, :public], is_public)
end 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
@ -3874,8 +3877,8 @@ test "it sends an email to user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
notify_email = Pleroma.Config.get([:instance, :notify_email]) notify_email = Config.get([:instance, :notify_email])
instance_name = Pleroma.Config.get([:instance, :name]) instance_name = Config.get([:instance, :name])
assert_email_sent( assert_email_sent(
from: {instance_name, notify_email}, from: {instance_name, notify_email},
@ -3907,11 +3910,11 @@ test "it returns 400 when user is not local", %{conn: conn, user: user} do
describe "POST /api/v1/pleroma/accounts/confirmation_resend" do describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
setup do setup do
setting = Pleroma.Config.get([:instance, :account_activation_required]) setting = Config.get([:instance, :account_activation_required])
unless setting do unless setting do
Pleroma.Config.put([:instance, :account_activation_required], true) Config.put([:instance, :account_activation_required], true)
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) on_exit(fn -> Config.put([:instance, :account_activation_required], setting) end)
end end
user = insert(:user) user = insert(:user)
@ -3935,8 +3938,8 @@ test "resend account confirmation email", %{conn: conn, user: user} do
|> json_response(:no_content) |> json_response(:no_content)
email = Pleroma.Emails.UserEmail.account_confirmation_email(user) email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
notify_email = Pleroma.Config.get([:instance, :notify_email]) notify_email = Config.get([:instance, :notify_email])
instance_name = Pleroma.Config.get([:instance, :name]) instance_name = Config.get([:instance, :name])
assert_email_sent( assert_email_sent(
from: {instance_name, notify_email}, from: {instance_name, notify_email},
@ -3945,4 +3948,84 @@ test "resend account confirmation email", %{conn: conn, user: user} do
) )
end end
end end
describe "GET /api/v1/suggestions" do
setup do
user = insert(:user)
other_user = insert(:user)
config = Config.get(:suggestions)
on_exit(fn -> Config.put(:suggestions, config) end)
host = Config.get([Pleroma.Web.Endpoint, :url, :host])
url500 = "http://test500?#{host}&#{user.nickname}"
url200 = "http://test200?#{host}&#{user.nickname}"
mock(fn
%{method: :get, url: ^url500} ->
%Tesla.Env{status: 500, body: "bad request"}
%{method: :get, url: ^url200} ->
%Tesla.Env{
status: 200,
body:
~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{
other_user.ap_id
}","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}])
}
end)
[user: user, other_user: other_user]
end
test "returns empty result when suggestions disabled", %{conn: conn, user: user} do
Config.put([:suggestions, :enabled], false)
res =
conn
|> assign(:user, user)
|> get("/api/v1/suggestions")
|> json_response(200)
assert res == []
end
test "returns error", %{conn: conn, user: user} do
Config.put([:suggestions, :enabled], true)
Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}")
res =
conn
|> assign(:user, user)
|> get("/api/v1/suggestions")
|> json_response(500)
assert res == "Something went wrong"
end
test "returns suggestions", %{conn: conn, user: user, other_user: other_user} do
Config.put([:suggestions, :enabled], true)
Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}")
res =
conn
|> assign(:user, user)
|> get("/api/v1/suggestions")
|> json_response(200)
assert res == [
%{
"acct" => "yj455",
"avatar" => "https://social.heldscal.la/avatar/201.jpeg",
"avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg",
"id" => 0
},
%{
"acct" => other_user.ap_id,
"avatar" => "https://social.heldscal.la/avatar/202.jpeg",
"avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg",
"id" => other_user.id
}
]
end
end
end end

View file

@ -0,0 +1,103 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do
use Pleroma.Web.ConnCase
alias Pleroma.Notification
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
describe "follow/3" do
test "returns error when user deactivated" do
follower = insert(:user)
user = insert(:user, local: true, info: %{deactivated: true})
{:error, error} = MastodonAPI.follow(follower, user)
assert error == "Could not follow user: You are deactivated."
end
test "following for user" do
follower = insert(:user)
user = insert(:user)
{:ok, follower} = MastodonAPI.follow(follower, user)
assert User.following?(follower, user)
end
test "returns ok if user already followed" do
follower = insert(:user)
user = insert(:user)
{:ok, follower} = User.follow(follower, user)
{:ok, follower} = MastodonAPI.follow(follower, refresh_record(user))
assert User.following?(follower, user)
end
end
describe "get_followers/2" do
test "returns user followers" do
follower1_user = insert(:user)
follower2_user = insert(:user)
user = insert(:user)
{:ok, _follower1_user} = User.follow(follower1_user, user)
{:ok, follower2_user} = User.follow(follower2_user, user)
assert MastodonAPI.get_followers(user, %{"limit" => 1}) == [follower2_user]
end
end
describe "get_friends/2" do
test "returns user friends" do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
followed_three = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
{:ok, user} = User.follow(user, followed_three)
res = MastodonAPI.get_friends(user)
assert length(res) == 3
assert Enum.member?(res, refresh_record(followed_three))
assert Enum.member?(res, refresh_record(followed_two))
assert Enum.member?(res, refresh_record(followed_one))
end
end
describe "get_notifications/2" do
test "returns notifications for user" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
{:ok, status1} = TwitterAPI.create_status(user, %{"status" => "Magi"})
{:ok, [notification]} = Notification.create_notifications(status)
{:ok, [notification1]} = Notification.create_notifications(status1)
res = MastodonAPI.get_notifications(subscriber)
assert Enum.member?(Enum.map(res, & &1.id), notification.id)
assert Enum.member?(Enum.map(res, & &1.id), notification1.id)
end
end
describe "get_scheduled_activities/2" do
test "returns user scheduled activities" do
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, schedule} = ScheduledActivity.create(user, attrs)
assert MastodonAPI.get_scheduled_activities(user) == [schedule]
end
end
end

View file

@ -85,6 +85,9 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} 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 @@ test "it shows MRF transparency data if enabled", %{conn: conn} 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 @@ test "it performs exclusions from MRF transparency data if configured", %{conn:
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, %{})