seconds_valid: 60,
method: Pleroma.Captcha.Kocaptcha
+config :pleroma, :hackney_pools,
+ federation: [
+ max_connections: 50,
+ timeout: 150_000
+ ],
+ media: [
+ max_connections: 50,
+ timeout: 150_000
+ ],
+ upload: [
+ max_connections: 25,
+ timeout: 300_000
+ ]
+
config :pleroma, Pleroma.Captcha.Kocaptcha, endpoint: "https://captcha.kotobank.ch"
# Upload configuration
uploader: Pleroma.Uploaders.Local,
filters: [],
proxy_remote: false,
- proxy_opts: []
+ proxy_opts: [
+ redirect_on_failure: false,
+ max_body_length: 25 * 1_048_576,
+ http: [
+ follow_redirect: true,
+ pool: :upload
+ ]
+ ]
config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads"
banner_upload_limit: 4_000_000,
registrations_open: true,
federating: true,
+ federation_reachability_timeout_days: 7,
allow_relay: true,
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
public: true,
Pleroma.HTML.Scrubber.Default
]
+# Deprecated, will be gone in 1.0
config :pleroma, :fe,
theme: "pleroma-dark",
logo: "/static/logo.png",
subject_line_behavior: "email",
always_show_subject_input: true
+config :pleroma, :frontend_configurations,
+ pleroma_fe: %{
+ theme: "pleroma-dark",
+ logo: "/static/logo.png",
+ background: "/images/city.jpg",
+ redirectRootNoLogin: "/main/all",
+ redirectRootLogin: "/main/friends",
+ showInstanceSpecificPanel: true,
+ scopeOptionsEnabled: false,
+ formattingOptionsEnabled: false,
+ collapseMessageWithSubject: false,
+ hidePostStats: false,
+ hideUserStats: false,
+ scopeCopy: true,
+ subjectLineBehavior: "email",
+ alwaysShowSubjectInput: true
+ }
+
config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
reject: [],
accept: []
-config :pleroma, :media_proxy, enabled: false
+config :pleroma, :rich_media, enabled: true
+
+config :pleroma, :media_proxy,
+ enabled: false,
+ proxy_opts: [
+ redirect_on_failure: false,
+ max_body_length: 25 * 1_048_576,
+ http: [
+ follow_redirect: true,
+ pool: :media
+ ]
+ ]
config :pleroma, :chat, enabled: true
* `invites_enabled`: Enable user invitations for admins (depends on `registrations_open: false`).
* `account_activation_required`: Require users to confirm their emails before signing in.
* `federating`: Enable federation with other instances
+ * `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default)
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog
See: [logger’s documentation](https://hexdocs.pm/logger/Logger.html) and [ex_syslogger’s documentation](https://hexdocs.pm/ex_syslogger/)
+
+## :frontend_configurations
+
+This can be used to configure a keyword list that keeps the configuration data for any kind of frontend. By default, settings for `pleroma_fe` are configured.
+
+Frontends can access these settings at `/api/pleroma/frontend_configurations`
+
+To add your own configuration for PleromaFE, use it like this:
+
+`config :pleroma, :frontend_configurations, pleroma_fe: %{redirectRootNoLogin: "/main/all", ...}`
+
+These settings need to be complete, they will override the defaults. See `priv/static/static/config.json` for the available keys.
+
## :fe
+__THIS IS DEPRECATED__
+
+If you are using this method, please change it to the `frontend_configurations` method. Please set this option to false in your config like this: `config :pleroma, :fe, false`.
+
This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:instance`` is set to false.
* `theme`: Which theme to use, they are defined in ``styles.json``
* `logo`: URL of the logo, defaults to Pleroma’s logo
-* `logo_mask`: Whenether to mask the logo
+* `logo_mask`: Whether to use only the logo's shape as a mask (true) or as a regular image (false)
* `logo_margin`: What margin to use around the logo
* `background`: URL of the background, unless viewing a user profile with a background that is set
* `redirect_root_no_login`: relative URL which indicates where to redirect when a user isn’t logged in.
* Pleroma.Web.Metadata.Providers.OpenGraph
* Pleroma.Web.Metadata.Providers.TwitterCard
* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews
+
+## :rich_media
+* `enabled`: if enabled the instance will parse metadata from attached links to generate link previews
+
+## :hackney_pools
+
+Advanced. Tweaks Hackney (http client) connections pools.
+
+There's three pools used:
+
+* `:federation` for the federation jobs.
+ You may want this pool max_connections to be at least equal to the number of federator jobs + retry queue jobs.
+* `:media` for rich media, media proxy
+* `:upload` for uploaded media (if using a remote uploader and `proxy_remote: true`)
+
+For each pool, the options are:
+
+* `max_connections` - how much connections a pool can hold
+* `timeout` - retention duration for connections
+
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ActivityPub do
- alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
+ alias Pleroma.{Activity, Repo, Object, Upload, User, Notification, Instances}
alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF}
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Federator
recipients: recipients
})
+ Task.start(fn ->
+ Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ end)
+
Notification.create_notifications(activity)
stream_out(activity)
{:ok, activity}
end
def publish(actor, activity) do
- followers =
+ remote_followers =
if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor)
followers |> Enum.filter(&(!&1.local))
public = is_public?(activity)
remote_inboxes =
- (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
+ (Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
+ |> Instances.filter_reachable()
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
json = Jason.encode!(data)
digest: digest
})
- @httpoison.post(
- inbox,
- json,
- [
- {"Content-Type", "application/activity+json"},
- {"signature", signature},
- {"digest", digest}
- ]
- )
+ with {:ok, %{status: code}} when code in 200..299 <-
+ result =
+ @httpoison.post(
+ inbox,
+ json,
+ [
+ {"Content-Type", "application/activity+json"},
+ {"signature", signature},
+ {"digest", digest}
+ ]
+ ) do
+ Instances.set_reachable(inbox)
+ result
+ else
+ {_post_result, response} ->
+ Instances.set_unreachable(inbox)
+ {:error, response}
+ end
end
# TODO:
}}
end
- def get("https://squeet.me/xrd/?uri=lain@squeet.me", _, _,
+ def get(
+ "https://squeet.me/xrd/?uri=lain@squeet.me",
+ _,
+ _,
Accept: "application/xrd+xml,application/jrd+json"
) do
{:ok,
}}
end
- def get("https://mst3k.interlinked.me/users/luciferMysticus", _, _,
+ def get(
+ "https://mst3k.interlinked.me/users/luciferMysticus",
+ _,
+ _,
Accept: "application/activity+json"
) do
{:ok,
}}
end
- def get("https://hubzilla.example.org/channel/kaniini", _, _,
+ def get(
+ "https://hubzilla.example.org/channel/kaniini",
+ _,
+ _,
Accept: "application/activity+json"
) do
{:ok,
}}
end
- def get("http://mastodon.example.org/@admin/99541947525187367", _, _,
+ def get(
+ "http://mastodon.example.org/@admin/99541947525187367",
+ _,
+ _,
Accept: "application/activity+json"
) do
{:ok,
}}
end
- def get("https://mstdn.io/users/mayuutann/statuses/99568293732299394", _, _,
+ def get(
+ "https://mstdn.io/users/mayuutann/statuses/99568293732299394",
+ _,
+ _,
Accept: "application/activity+json"
) do
{:ok,
}}
end
- def get("https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056", _, _,
+ def get(
+ "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056",
+ _,
+ _,
Accept: "application/atom+xml"
) do
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/sakamoto.atom")}}
%Tesla.Env{status: 200, body: File.read!("test/fixtures/httpoison_mock/squeet.me_host_meta")}}
end
- def get("https://squeet.me/xrd?uri=lain@squeet.me", _, _,
+ def get(
+ "https://squeet.me/xrd?uri=lain@squeet.me",
+ _,
+ _,
Accept: "application/xrd+xml,application/jrd+json"
) do
{:ok,
}}
end
- def get("http://framatube.org/main/xrd?uri=framasoft@framatube.org", _, _,
+ def get(
+ "http://framatube.org/main/xrd?uri=framasoft@framatube.org",
+ _,
+ _,
Accept: "application/xrd+xml,application/jrd+json"
) do
{:ok,
}}
end
- def get("http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de", _, _,
+ def get(
+ "http://gnusocial.de/main/xrd?uri=winterdienst@gnusocial.de",
+ _,
+ _,
Accept: "application/xrd+xml,application/jrd+json"
) do
{:ok,
}}
end
- def get("https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de", _, _,
+ def get(
+ "https://gerzilla.de/xrd/?uri=kaniini@gerzilla.de",
+ _,
+ _,
Accept: "application/xrd+xml,application/jrd+json"
) do
{:ok,
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}}
end
+ def get("http://example.com/malformed", _, _, _) do
+ {:ok,
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/malformed-data.html")}}
+ end
+
def get("http://example.com/empty", _, _, _) do
{:ok, %Tesla.Env{status: 200, body: "hello"}}
end
+ def get("http://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
def get(url, query, body, headers) do
{:error,
"Not implemented the mock response for get #{inspect(url)}, #{query}, #{inspect(body)}, #{
}}
end
+ def post("http://200.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: ""
+ }}
+ end
+
+ def post("http://connrefused.site" <> _, _, _, _) do
+ {:error, :connrefused}
+ end
+
+ def post("http://404.site" <> _, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 404,
+ body: ""
+ }}
+ end
+
def post(url, _query, _body, _headers) do
{:error, "Not implemented the mock response for post #{inspect(url)}"}
end
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
- alias Pleroma.{Activity, Object, User}
+ alias Pleroma.{Activity, Object, User, Instances}
alias Pleroma.Builders.ActivityBuilder
import Pleroma.Factory
import Tesla.Mock
+ import Mock
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
"in_reply_to_status_id" => private_activity_2.id
})
- assert user1.following == [user3.ap_id <> "/followers", user1.ap_id]
-
activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
assert [public_activity, private_activity_1, private_activity_3] == activities
assert 3 = length(activities)
end
+ describe "publish_one/1" do
+ test_with_mock "it calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://404.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://connrefused.site/users/nick1/inbox"
+
+ assert {:error, _} =
+ ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ assert called(Instances.set_unreachable(inbox))
+ end
+
+ test_with_mock "it does NOT call `Instances.set_unreachable` if target is reachable",
+ Instances,
+ [:passthrough],
+ [] do
+ actor = insert(:user)
+ inbox = "http://200.site/users/nick1/inbox"
+
+ assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
+
+ refute called(Instances.set_unreachable(inbox))
+ end
+ end
+
def data_uri do
File.read!("test/fixtures/avatar_data_uri")
end