summaryrefslogtreecommitdiff
path: root/lib/pleroma/web
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web')
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex107
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex12
-rw-r--r--lib/pleroma/web/activity_pub/builder.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex4
-rw-r--r--lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex56
-rw-r--r--lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex22
-rw-r--r--lib/pleroma/web/activity_pub/object_validator.ex31
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex (renamed from lib/pleroma/web/activity_pub/object_validators/audio_validator.ex)40
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex21
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex134
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex2
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/common_fixes.ex13
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex8
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/event_validator.ex5
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/note_validator.ex66
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/question_validator.ex9
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex24
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex38
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex20
-rw-r--r--lib/pleroma/web/activity_pub/side_effects.ex14
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex73
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex2
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex39
-rw-r--r--lib/pleroma/web/admin_api/controllers/admin_api_controller.ex24
-rw-r--r--lib/pleroma/web/admin_api/controllers/chat_controller.ex85
-rw-r--r--lib/pleroma/web/admin_api/controllers/config_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/controllers/instance_document_controller.ex41
-rw-r--r--lib/pleroma/web/admin_api/controllers/invite_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex (renamed from lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex)2
-rw-r--r--lib/pleroma/web/admin_api/controllers/relay_controller.ex18
-rw-r--r--lib/pleroma/web/admin_api/controllers/report_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/controllers/status_controller.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/account_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/chat_view.ex30
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex2
-rw-r--r--lib/pleroma/web/admin_api/views/status_view.ex3
-rw-r--r--lib/pleroma/web/api_spec.ex13
-rw-r--r--lib/pleroma/web/api_spec/cast_and_validate.ex4
-rw-r--r--lib/pleroma/web/api_spec/helpers.ex6
-rw-r--r--lib/pleroma/web/api_spec/operations/account_operation.ex7
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/chat_operation.ex96
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex115
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex (renamed from lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex)0
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/relay_operation.ex12
-rw-r--r--lib/pleroma/web/api_spec/operations/chat_operation.ex3
-rw-r--r--lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex2
-rw-r--r--lib/pleroma/web/api_spec/operations/list_operation.ex21
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex139
-rw-r--r--lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex123
-rw-r--r--lib/pleroma/web/api_spec/operations/user_import_operation.ex80
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat.ex2
-rw-r--r--lib/pleroma/web/api_spec/schemas/chat_message.ex3
-rw-r--r--lib/pleroma/web/api_spec/schemas/scheduled_status.ex4
-rw-r--r--lib/pleroma/web/api_spec/schemas/status.ex2
-rw-r--r--lib/pleroma/web/auth/pleroma_authenticator.ex4
-rw-r--r--lib/pleroma/web/auth/totp_authenticator.ex2
-rw-r--r--lib/pleroma/web/common_api.ex (renamed from lib/pleroma/web/common_api/common_api.ex)25
-rw-r--r--lib/pleroma/web/common_api/activity_draft.ex2
-rw-r--r--lib/pleroma/web/common_api/utils.ex13
-rw-r--r--lib/pleroma/web/controller_helper.ex20
-rw-r--r--lib/pleroma/web/endpoint.ex58
-rw-r--r--lib/pleroma/web/fallback/redirect_controller.ex (renamed from lib/pleroma/web/fallback_redirect_controller.ex)4
-rw-r--r--lib/pleroma/web/fed_sockets.ex185
-rw-r--r--lib/pleroma/web/fed_sockets/fed_registry.ex185
-rw-r--r--lib/pleroma/web/fed_sockets/fed_socket.ex137
-rw-r--r--lib/pleroma/web/fed_sockets/fetch_registry.ex151
-rw-r--r--lib/pleroma/web/fed_sockets/incoming_handler.ex88
-rw-r--r--lib/pleroma/web/fed_sockets/ingester_worker.ex33
-rw-r--r--lib/pleroma/web/fed_sockets/outgoing_handler.ex151
-rw-r--r--lib/pleroma/web/fed_sockets/socket_info.ex52
-rw-r--r--lib/pleroma/web/fed_sockets/supervisor.ex59
-rw-r--r--lib/pleroma/web/federator.ex (renamed from lib/pleroma/web/federator/federator.ex)16
-rw-r--r--lib/pleroma/web/feed/tag_controller.ex17
-rw-r--r--lib/pleroma/web/feed/user_controller.ex32
-rw-r--r--lib/pleroma/web/instance_document.ex62
-rw-r--r--lib/pleroma/web/mailer/subscription_controller.ex4
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/app_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/auth_controller.ex12
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/filter_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/instance_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/list_controller.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/marker_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/media_controller.ex3
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/notification_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/poll_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/report_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/search_controller.ex4
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex19
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex14
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex33
-rw-r--r--lib/pleroma/web/mastodon_api/websocket_handler.ex10
-rw-r--r--lib/pleroma/web/media_proxy.ex (renamed from lib/pleroma/web/media_proxy/media_proxy.ex)87
-rw-r--r--lib/pleroma/web/media_proxy/invalidation.ex4
-rw-r--r--lib/pleroma/web/media_proxy/invalidation/http.ex (renamed from lib/pleroma/web/media_proxy/invalidations/http.ex)2
-rw-r--r--lib/pleroma/web/media_proxy/invalidation/script.ex (renamed from lib/pleroma/web/media_proxy/invalidations/script.ex)0
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy_controller.ex195
-rw-r--r--lib/pleroma/web/metadata/providers/feed.ex (renamed from lib/pleroma/web/metadata/feed.ex)0
-rw-r--r--lib/pleroma/web/metadata/providers/open_graph.ex (renamed from lib/pleroma/web/metadata/opengraph.ex)2
-rw-r--r--lib/pleroma/web/metadata/providers/provider.ex (renamed from lib/pleroma/web/metadata/provider.ex)0
-rw-r--r--lib/pleroma/web/metadata/providers/rel_me.ex (renamed from lib/pleroma/web/metadata/rel_me.ex)0
-rw-r--r--lib/pleroma/web/metadata/providers/restrict_indexing.ex (renamed from lib/pleroma/web/metadata/restrict_indexing.ex)7
-rw-r--r--lib/pleroma/web/metadata/providers/twitter_card.ex (renamed from lib/pleroma/web/metadata/twitter_card.ex)2
-rw-r--r--lib/pleroma/web/metadata/utils.ex2
-rw-r--r--lib/pleroma/web/mongoose_im/mongoose_im_controller.ex (renamed from lib/pleroma/web/mongooseim/mongoose_im_controller.ex)4
-rw-r--r--lib/pleroma/web/o_auth.ex (renamed from lib/pleroma/web/oauth.ex)0
-rw-r--r--lib/pleroma/web/o_auth/app.ex (renamed from lib/pleroma/web/oauth/app.ex)0
-rw-r--r--lib/pleroma/web/o_auth/authorization.ex (renamed from lib/pleroma/web/oauth/authorization.ex)0
-rw-r--r--lib/pleroma/web/o_auth/fallback_controller.ex (renamed from lib/pleroma/web/oauth/fallback_controller.ex)0
-rw-r--r--lib/pleroma/web/o_auth/mfa_controller.ex (renamed from lib/pleroma/web/oauth/mfa_controller.ex)0
-rw-r--r--lib/pleroma/web/o_auth/mfa_view.ex (renamed from lib/pleroma/web/oauth/mfa_view.ex)0
-rw-r--r--lib/pleroma/web/o_auth/o_auth_controller.ex (renamed from lib/pleroma/web/oauth/oauth_controller.ex)15
-rw-r--r--lib/pleroma/web/o_auth/o_auth_view.ex (renamed from lib/pleroma/web/oauth/oauth_view.ex)0
-rw-r--r--lib/pleroma/web/o_auth/scopes.ex (renamed from lib/pleroma/web/oauth/scopes.ex)2
-rw-r--r--lib/pleroma/web/o_auth/token.ex (renamed from lib/pleroma/web/oauth/token.ex)25
-rw-r--r--lib/pleroma/web/o_auth/token/query.ex (renamed from lib/pleroma/web/oauth/token/query.ex)6
-rw-r--r--lib/pleroma/web/o_auth/token/strategy/refresh_token.ex (renamed from lib/pleroma/web/oauth/token/strategy/refresh_token.ex)2
-rw-r--r--lib/pleroma/web/o_auth/token/strategy/revoke.ex (renamed from lib/pleroma/web/oauth/token/strategy/revoke.ex)0
-rw-r--r--lib/pleroma/web/o_auth/token/utils.ex (renamed from lib/pleroma/web/oauth/token/utils.ex)0
-rw-r--r--lib/pleroma/web/o_status/o_status_controller.ex (renamed from lib/pleroma/web/ostatus/ostatus_controller.ex)23
-rw-r--r--lib/pleroma/web/oauth/token/clean_worker.ex38
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/account_controller.ex11
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/chat_controller.ex73
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex137
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex125
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex12
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/notification_controller.ex8
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex61
-rw-r--r--lib/pleroma/web/pleroma_api/views/scrobble_view.ex4
-rw-r--r--lib/pleroma/web/plug.ex8
-rw-r--r--lib/pleroma/web/plugs/admin_secret_authentication_plug.ex60
-rw-r--r--lib/pleroma/web/plugs/authentication_plug.ex79
-rw-r--r--lib/pleroma/web/plugs/basic_auth_decoder_plug.ex25
-rw-r--r--lib/pleroma/web/plugs/cache.ex136
-rw-r--r--lib/pleroma/web/plugs/digest_plug.ex14
-rw-r--r--lib/pleroma/web/plugs/ensure_authenticated_plug.ex41
-rw-r--r--lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex35
-rw-r--r--lib/pleroma/web/plugs/ensure_user_key_plug.ex18
-rw-r--r--lib/pleroma/web/plugs/expect_authenticated_check_plug.ex20
-rw-r--r--lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex21
-rw-r--r--lib/pleroma/web/plugs/federating_plug.ex32
-rw-r--r--lib/pleroma/web/plugs/frontend_static.ex59
-rw-r--r--lib/pleroma/web/plugs/http_security_plug.ex225
-rw-r--r--lib/pleroma/web/plugs/http_signature_plug.ex65
-rw-r--r--lib/pleroma/web/plugs/idempotency_plug.ex84
-rw-r--r--lib/pleroma/web/plugs/instance_static.ex53
-rw-r--r--lib/pleroma/web/plugs/legacy_authentication_plug.ex41
-rw-r--r--lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex71
-rw-r--r--lib/pleroma/web/plugs/o_auth_plug.ex120
-rw-r--r--lib/pleroma/web/plugs/o_auth_scopes_plug.ex77
-rw-r--r--lib/pleroma/web/plugs/plug_helper.ex40
-rw-r--r--lib/pleroma/web/plugs/rate_limiter.ex267
-rw-r--r--lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex54
-rw-r--r--lib/pleroma/web/plugs/rate_limiter/supervisor.ex20
-rw-r--r--lib/pleroma/web/plugs/remote_ip.ex48
-rw-r--r--lib/pleroma/web/plugs/session_authentication_plug.ex21
-rw-r--r--lib/pleroma/web/plugs/set_format_plug.ex24
-rw-r--r--lib/pleroma/web/plugs/set_locale_plug.ex63
-rw-r--r--lib/pleroma/web/plugs/set_user_session_id_plug.ex19
-rw-r--r--lib/pleroma/web/plugs/static_fe_plug.ex26
-rw-r--r--lib/pleroma/web/plugs/trailing_format_plug.ex42
-rw-r--r--lib/pleroma/web/plugs/uploaded_media.ex107
-rw-r--r--lib/pleroma/web/plugs/user_enabled_plug.ex23
-rw-r--r--lib/pleroma/web/plugs/user_fetcher_plug.ex21
-rw-r--r--lib/pleroma/web/plugs/user_is_admin_plug.ex24
-rw-r--r--lib/pleroma/web/preload/providers/instance.ex (renamed from lib/pleroma/web/preload/instance.ex)11
-rw-r--r--lib/pleroma/web/preload/providers/provider.ex (renamed from lib/pleroma/web/preload/provider.ex)0
-rw-r--r--lib/pleroma/web/preload/providers/timelines.ex (renamed from lib/pleroma/web/preload/timelines.ex)0
-rw-r--r--lib/pleroma/web/preload/providers/user.ex (renamed from lib/pleroma/web/preload/user.ex)0
-rw-r--r--lib/pleroma/web/push.ex (renamed from lib/pleroma/web/push/push.ex)0
-rw-r--r--lib/pleroma/web/push/impl.ex12
-rw-r--r--lib/pleroma/web/rel_me.ex15
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex24
-rw-r--r--lib/pleroma/web/rich_media/parser.ex72
-rw-r--r--lib/pleroma/web/rich_media/parser/ttl.ex7
-rw-r--r--lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex (renamed from lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex)6
-rw-r--r--lib/pleroma/web/rich_media/parsers/o_embed.ex (renamed from lib/pleroma/web/rich_media/parsers/oembed_parser.ex)0
-rw-r--r--lib/pleroma/web/rich_media/parsers/ttl/ttl.ex3
-rw-r--r--lib/pleroma/web/router.ex147
-rw-r--r--lib/pleroma/web/static_fe/static_fe_controller.ex177
-rw-r--r--lib/pleroma/web/streamer.ex (renamed from lib/pleroma/web/streamer/streamer.ex)70
-rw-r--r--lib/pleroma/web/templates/layout/app.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/email_styled.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/metadata_player.html.eex2
-rw-r--r--lib/pleroma/web/templates/layout/static_fe.html.eex2
-rw-r--r--lib/pleroma/web/twitter_api/controller.ex (renamed from lib/pleroma/web/twitter_api/twitter_api_controller.ex)4
-rw-r--r--lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex7
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex45
-rw-r--r--lib/pleroma/web/twitter_api/views/util_view.ex6
-rw-r--r--lib/pleroma/web/views/email_view.ex4
-rw-r--r--lib/pleroma/web/views/mailer/subscription_view.ex4
-rw-r--r--lib/pleroma/web/web.ex239
-rw-r--r--lib/pleroma/web/web_finger.ex (renamed from lib/pleroma/web/web_finger/web_finger.ex)0
-rw-r--r--lib/pleroma/web/web_finger/web_finger_controller.ex4
-rw-r--r--lib/pleroma/web/xml.ex (renamed from lib/pleroma/web/xml/xml.ex)0
211 files changed, 5469 insertions, 1384 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index e4eafc8ac..3543f7f73 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -5,7 +5,6 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
- alias Pleroma.ActivityExpiration
alias Pleroma.Config
alias Pleroma.Constants
alias Pleroma.Conversation
@@ -85,7 +84,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp increase_replies_count_if_reply(_create_data), do: :noop
- @object_types ~w[ChatMessage Question Answer Audio Event]
+ @object_types ~w[ChatMessage Question Answer Audio Video Event Article]
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do
@@ -102,7 +101,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
local: local,
recipients: recipients,
actor: object["actor"]
- }) do
+ }),
+ # TODO: add tests for expired activities, when Note type will be supported in new pipeline
+ {:ok, _} <- maybe_create_activity_expiration(activity) do
{:ok, activity, meta}
end
end
@@ -111,23 +112,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake),
- true <- bypass_actor_check || check_actor_is_active(map["actor"]),
- {_, true} <- {:remote_limit_error, check_remote_limit(map)},
+ {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])},
+ {_, true} <- {:remote_limit_pass, check_remote_limit(map)},
{:ok, map} <- MRF.filter(map),
{recipients, _, _} = get_recipients(map),
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
{:containment, :ok} <- {:containment, Containment.contain_child(map)},
- {:ok, map, object} <- insert_full_object(map) do
- {:ok, activity} =
- %Activity{
- data: map,
- local: local,
- actor: map["actor"],
- recipients: recipients
- }
- |> Repo.insert()
- |> maybe_create_activity_expiration()
-
+ {:ok, map, object} <- insert_full_object(map),
+ {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do
# Splice in the child object if we have one.
activity = Maps.put_if_present(activity, :object, object)
@@ -138,6 +130,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
%Activity{} = activity ->
{:ok, activity}
+ {:actor_check, _} ->
+ {:error, false}
+
+ {:containment, _} = error ->
+ error
+
+ {:error, _} = error ->
+ error
+
{:fake, true, map, recipients} ->
activity = %Activity{
data: map,
@@ -150,8 +151,24 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
{:ok, activity}
- error ->
- {:error, error}
+ {:remote_limit_pass, _} ->
+ {:error, :remote_limit}
+
+ {:reject, _} = e ->
+ {:error, e}
+ end
+ end
+
+ defp insert_activity_with_expiration(data, local, recipients) do
+ struct = %Activity{
+ data: data,
+ local: local,
+ actor: data["actor"],
+ recipients: recipients
+ }
+
+ with {:ok, activity} <- Repo.insert(struct) do
+ maybe_create_activity_expiration(activity)
end
end
@@ -164,13 +181,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
stream_out_participations(participations)
end
- defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
- with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
+ defp maybe_create_activity_expiration(
+ %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity
+ ) do
+ with {:ok, _job} <-
+ Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+ activity_id: activity.id,
+ expires_at: expires_at
+ }) do
{:ok, activity}
end
end
- defp maybe_create_activity_expiration(result), do: result
+ defp maybe_create_activity_expiration(activity), do: {:ok, activity}
defp create_or_bump_conversation(activity, actor) do
with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
@@ -767,7 +790,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
[activity, object] in query,
where:
fragment(
- "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?",
+ """
+ ?->>'type' != 'Create' -- This isn't a Create
+ OR ?->>'inReplyTo' is null -- this isn't a reply
+ OR ? && array_remove(?, ?) -- The recipient is us or one of our friends,
+ -- unless they are the author (because authors
+ -- are also part of the recipients). This leads
+ -- to a bug that self-replies by friends won't
+ -- show up.
+ OR ? = ? -- The actor is us
+ """,
+ activity.data,
object.data,
^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],
activity.recipients,
@@ -818,7 +851,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
from(
[activity, object: o] in query,
where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
- where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
+ where:
+ fragment(
+ "((not (? && ?)) or ? = ?)",
+ activity.recipients,
+ ^blocked_ap_ids,
+ activity.actor,
+ ^user.ap_id
+ ),
where:
fragment(
"recipients_contain_blocked_domains(?, ?) = false",
@@ -1188,7 +1228,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{String.trim(name, ":"), url}
end)
- locked = data["manuallyApprovesFollowers"] || false
+ is_locked = data["manuallyApprovesFollowers"] || false
capabilities = data["capabilities"] || %{}
accepts_chat_messages = capabilities["acceptsChatMessages"]
data = Transmogrifier.maybe_fix_user_object(data)
@@ -1217,14 +1257,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
banner: banner,
fields: fields,
emoji: emojis,
- locked: locked,
+ is_locked: is_locked,
discoverable: discoverable,
invisible: invisible,
avatar: avatar,
name: data["name"],
follower_address: data["followers"],
following_address: data["following"],
- bio: data["summary"],
+ bio: data["summary"] || "",
actor_type: actor_type,
also_known_as: Map.get(data, "alsoKnownAs", []),
public_key: public_key,
@@ -1247,10 +1287,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_follow_information_for_user(user) do
with {:ok, following_data} <-
- Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
+ Fetcher.fetch_and_contain_remote_object_from_id(user.following_address,
+ force_http: true
+ ),
{:ok, hide_follows} <- collection_private(following_data),
{:ok, followers_data} <-
- Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
+ Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true),
{:ok, hide_followers} <- collection_private(followers_data) do
{:ok,
%{
@@ -1324,11 +1366,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def fetch_and_prepare_user_from_ap_id(ap_id) do
- with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
+ def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do
+ with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts),
{:ok, data} <- user_data_from_user_object(data) do
{:ok, maybe_update_follow_information(data)}
else
+ # If this has been deleted, only log a debug and not an error
{:error, "Object has been deleted" = e} ->
Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
@@ -1367,13 +1410,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
- def make_user_from_ap_id(ap_id) do
+ def make_user_from_ap_id(ap_id, opts \\ []) do
user = User.get_cached_by_ap_id(ap_id)
if user && !User.ap_enabled?(user) do
Transmogrifier.upgrade_user_from_ap_id(ap_id)
else
- with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
+ with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do
if user do
user
|> User.remote_user_changeset(data)
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 732c44271..44f09be75 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Delivery
alias Pleroma.Object
alias Pleroma.Object.Fetcher
- alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
@@ -23,8 +22,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.Endpoint
- alias Pleroma.Web.FederatingPlug
alias Pleroma.Web.Federator
+ alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
+ alias Pleroma.Web.Plugs.FederatingPlug
require Logger
@@ -45,8 +45,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
)
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:upload_media])
+
plug(
- Pleroma.Plugs.Cache,
+ Pleroma.Web.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
when action in [:activity, :object]
)
@@ -412,7 +414,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
object =
object
|> Map.merge(Map.take(params, ["to", "cc"]))
- |> Map.put("attributedTo", user.ap_id())
+ |> Map.put("attributedTo", user.ap_id)
|> Transmogrifier.fix_object()
ActivityPub.create(%{
@@ -456,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
%{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname} = params
) do
- actor = user.ap_id()
+ actor = user.ap_id
params =
params
diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex
index 9a7b7d9de..298aff6b7 100644
--- a/lib/pleroma/web/activity_pub/builder.ex
+++ b/lib/pleroma/web/activity_pub/builder.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.Builder do
@moduledoc """
This module builds the objects. Meant to be used for creating local objects.
diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
index 7b4c78e0f..bee47b4ed 100644
--- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex
@@ -31,10 +31,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do
defp maybe_add_expiration(activity) do
days = Pleroma.Config.get([:mrf_activity_expiration, :days], 365)
- expires_at = NaiveDateTime.utc_now() |> Timex.shift(days: days)
+ expires_at = DateTime.utc_now() |> Timex.shift(days: days)
with %{"expires_at" => existing_expires_at} <- activity,
- :lt <- NaiveDateTime.compare(existing_expires_at, expires_at) do
+ :lt <- DateTime.compare(existing_expires_at, expires_at) do
activity
else
_ -> Map.put(activity, "expires_at", expires_at)
diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
new file mode 100644
index 000000000..ea9c3d3f5
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do
+ alias Pleroma.User
+ @behaviour Pleroma.Web.ActivityPub.MRF
+ @moduledoc "Remove bot posts from federated timeline"
+
+ require Pleroma.Constants
+
+ defp check_by_actor_type(user), do: user.actor_type in ["Application", "Service"]
+ defp check_by_nickname(user), do: Regex.match?(~r/bot@|ebooks@/i, user.nickname)
+
+ defp check_if_bot(user), do: check_by_actor_type(user) or check_by_nickname(user)
+
+ @impl true
+ def filter(
+ %{
+ "type" => "Create",
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor,
+ "object" => object
+ } = message
+ ) do
+ user = User.get_cached_by_ap_id(actor)
+ isbot = check_if_bot(user)
+
+ if isbot and Enum.member?(to, Pleroma.Constants.as_public()) do
+ to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
+ cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()]
+
+ object =
+ object
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+
+ message =
+ message
+ |> Map.put("to", to)
+ |> Map.put("cc", cc)
+ |> Map.put("object", object)
+
+ {:ok, message}
+ else
+ {:ok, message}
+ end
+ end
+
+ @impl true
+ def filter(message), do: {:ok, message}
+
+ @impl true
+ def describe, do: {:ok, %{}}
+end
diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
index dfab105a3..0fb05d3c4 100644
--- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex
@@ -12,23 +12,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
require Logger
- @options [
- pool: :media
+ @adapter_options [
+ pool: :media,
+ recv_timeout: 10_000
]
def perform(:prefetch, url) do
- Logger.debug("Prefetching #{inspect(url)}")
+ # Fetching only proxiable resources
+ if MediaProxy.enabled?() and MediaProxy.url_proxiable?(url) do
+ # If preview proxy is enabled, it'll also hit media proxy (so we're caching both requests)
+ prefetch_url = MediaProxy.preview_url(url)
- opts =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.put(@options, :recv_timeout, 10_000)
- else
- @options
- end
+ Logger.debug("Prefetching #{inspect(url)} as #{inspect(prefetch_url)}")
- url
- |> MediaProxy.url()
- |> HTTP.get([], adapter: opts)
+ HTTP.get(prefetch_url, [], @adapter_options)
+ end
end
def perform(:preload, %{"object" => %{"attachment" => attachments}} = _message) do
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index b77c06395..bd0a2a8dc 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -12,11 +12,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
+ alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
- alias Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
@@ -149,10 +151,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
end
end
- def validate(%{"type" => "Audio"} = object, meta) do
+ def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
with {:ok, object} <-
object
- |> AudioValidator.cast_and_validate()
+ |> AudioVideoValidator.cast_and_validate()
+ |> Ecto.Changeset.apply_action(:insert) do
+ object = stringify_keys(object)
+ {:ok, object, meta}
+ end
+ end
+
+ def validate(%{"type" => "Article"} = object, meta) do
+ with {:ok, object} <-
+ object
+ |> ArticleNoteValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
@@ -198,7 +210,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
meta
)
- when objtype in ~w[Question Answer Audio Event] do
+ when objtype in ~w[Question Answer Audio Video Event Article] do
with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
@@ -232,14 +244,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
AnswerValidator.cast_and_apply(object)
end
- def cast_and_apply(%{"type" => "Audio"} = object) do
- AudioValidator.cast_and_apply(object)
+ def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do
+ AudioVideoValidator.cast_and_apply(object)
end
def cast_and_apply(%{"type" => "Event"} = object) do
EventValidator.cast_and_apply(object)
end
+ def cast_and_apply(%{"type" => "Article"} = object) do
+ ArticleNoteValidator.cast_and_apply(object)
+ end
+
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
# is_struct/1 isn't present in Elixir 1.8.x
@@ -262,7 +278,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
def stringify_keys(object), do: object
def fetch_actor(object) do
- with {:ok, actor} <- ObjectValidators.ObjectID.cast(object["actor"]) do
+ with actor <- Containment.get_actor(object),
+ {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
User.get_or_fetch_by_ap_id(actor)
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
index d1869f188..5b7dad517 100644
--- a/lib/pleroma/web/activity_pub/object_validators/audio_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_validator.ex
@@ -2,13 +2,14 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@@ -24,29 +25,31 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
# TODO: Write type
field(:tag, {:array, :map}, default: [])
field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
field(:content, :string)
+
field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
# TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID)
- field(:summary, :string)
field(:published, ObjectValidators.DateTime)
- # TODO: Write type
- field(:emoji, :map, default: %{})
+ field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
field(:like_count, :integer, default: 0)
field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, :string)
+ field(:inReplyTo, ObjectValidators.ObjectID)
field(:url, ObjectValidators.Uri)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
- field(:likes, {:array, :string}, default: [])
- field(:announcements, {:array, :string}, default: [])
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
end
def cast_and_apply(data) do
@@ -62,19 +65,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
end
def cast_data(data) do
+ data = fix(data)
+
%__MODULE__{}
|> changeset(data)
end
- defp fix_url(%{"url" => url} = data) when is_list(url) do
- attachment =
- Enum.find(url, fn x -> is_map(x) and String.starts_with?(x["mimeType"], "audio/") end)
-
- link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
-
- data
- |> Map.put("attachment", [attachment])
- |> Map.put("url", link_element["href"])
+ defp fix_url(%{"url" => url} = data) when is_map(url) do
+ Map.put(data, "url", url["href"])
end
defp fix_url(data), do: data
@@ -83,7 +81,9 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
+ |> CommonFixes.fix_actor()
|> fix_url()
+ |> Transmogrifier.fix_emoji()
end
def changeset(struct, data) do
@@ -96,8 +96,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioValidator do
def validate_data(data_cng) do
data_cng
- |> validate_inclusion(:type, ["Audio"])
- |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
+ |> validate_inclusion(:type, ["Article", "Note"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :context_id])
|> CommonValidations.validate_any_presence([:cc, :to])
|> CommonValidations.validate_fields_match([:actor, :attributedTo])
|> CommonValidations.validate_actor_presence()
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index c8b148280..df102a134 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
use Ecto.Schema
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator
import Ecto.Changeset
@@ -15,7 +16,11 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
field(:mediaType, :string, default: "application/octet-stream")
field(:name, :string)
- embeds_many(:url, UrlObjectValidator)
+ embeds_many :url, UrlObjectValidator, primary_key: false do
+ field(:type, :string)
+ field(:href, ObjectValidators.Uri)
+ field(:mediaType, :string, default: "application/octet-stream")
+ end
end
def cast_and_validate(data) do
@@ -37,7 +42,18 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
struct
|> cast(data, [:type, :mediaType, :name])
- |> cast_embed(:url, required: true)
+ |> cast_embed(:url, with: &url_changeset/2)
+ |> validate_inclusion(:type, ~w[Link Document Audio Image Video])
+ |> validate_required([:type, :mediaType, :url])
+ end
+
+ def url_changeset(struct, data) do
+ data = fix_media_type(data)
+
+ struct
+ |> cast(data, [:type, :href, :mediaType])
+ |> validate_inclusion(:type, ["Link"])
+ |> validate_required([:type, :href, :mediaType])
end
def fix_media_type(data) do
@@ -75,6 +91,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
def validate_data(cng) do
cng
+ |> validate_inclusion(:type, ~w[Document Audio Image Video])
|> validate_required([:mediaType, :url, :type])
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
new file mode 100644
index 000000000..16973e5db
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/object_validators/audio_video_validator.ex
@@ -0,0 +1,134 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator do
+ use Ecto.Schema
+
+ alias Pleroma.EarmarkRenderer
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators
+ alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
+
+ import Ecto.Changeset
+
+ @primary_key false
+ @derive Jason.Encoder
+
+ embedded_schema do
+ field(:id, ObjectValidators.ObjectID, primary_key: true)
+ field(:to, ObjectValidators.Recipients, default: [])
+ field(:cc, ObjectValidators.Recipients, default: [])
+ field(:bto, ObjectValidators.Recipients, default: [])
+ field(:bcc, ObjectValidators.Recipients, default: [])
+ # TODO: Write type
+ field(:tag, {:array, :map}, default: [])
+ field(:type, :string)
+
+ field(:name, :string)
+ field(:summary, :string)
+ field(:content, :string)
+
+ field(:context, :string)
+ # short identifier for PleromaFE to group statuses by context
+ field(:context_id, :integer)
+
+ # TODO: Remove actor on objects
+ field(:actor, ObjectValidators.ObjectID)
+
+ field(:attributedTo, ObjectValidators.ObjectID)
+ field(:published, ObjectValidators.DateTime)
+ field(:emoji, ObjectValidators.Emoji, default: %{})
+ field(:sensitive, :boolean, default: false)
+ embeds_many(:attachment, AttachmentValidator)
+ field(:replies_count, :integer, default: 0)
+ field(:like_count, :integer, default: 0)
+ field(:announcement_count, :integer, default: 0)
+ field(:inReplyTo, ObjectValidators.ObjectID)
+ field(:url, ObjectValidators.Uri)
+
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
+ end
+
+ def cast_and_apply(data) do
+ data
+ |> cast_data
+ |> apply_action(:insert)
+ end
+
+ def cast_and_validate(data) do
+ data
+ |> cast_data()
+ |> validate_data()
+ end
+
+ def cast_data(data) do
+ %__MODULE__{}
+ |> changeset(data)
+ end
+
+ defp fix_url(%{"url" => url} = data) when is_list(url) do
+ attachment =
+ Enum.find(url, fn x ->
+ mime_type = x["mimeType"] || x["mediaType"] || ""
+
+ is_map(x) and String.starts_with?(mime_type, ["video/", "audio/"])
+ end)
+
+ link_element =
+ Enum.find(url, fn x ->
+ mime_type = x["mimeType"] || x["mediaType"] || ""
+
+ is_map(x) and mime_type == "text/html"
+ end)
+
+ data
+ |> Map.put("attachment", [attachment])
+ |> Map.put("url", link_element["href"])
+ end
+
+ defp fix_url(data), do: data
+
+ defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = data)
+ when is_binary(content) do
+ content =
+ content
+ |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
+ |> Pleroma.HTML.filter_tags()
+
+ Map.put(data, "content", content)
+ end
+
+ defp fix_content(data), do: data
+
+ defp fix(data) do
+ data
+ |> CommonFixes.fix_defaults()
+ |> CommonFixes.fix_attribution()
+ |> CommonFixes.fix_actor()
+ |> Transmogrifier.fix_emoji()
+ |> fix_url()
+ |> fix_content()
+ end
+
+ def changeset(struct, data) do
+ data = fix(data)
+
+ struct
+ |> cast(data, __schema__(:fields) -- [:attachment])
+ |> cast_embed(:attachment)
+ end
+
+ def validate_data(data_cng) do
+ data_cng
+ |> validate_inclusion(:type, ["Audio", "Video"])
+ |> validate_required([:id, :actor, :attributedTo, :type, :context, :attachment])
+ |> CommonValidations.validate_any_presence([:cc, :to])
+ |> CommonValidations.validate_fields_match([:actor, :attributedTo])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_host_match()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
index 91b475393..6acd4a771 100644
--- a/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/chat_message_validator.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator do
field(:content, ObjectValidators.SafeText)
field(:actor, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
- field(:emoji, :map, default: %{})
+ field(:emoji, ObjectValidators.Emoji, default: %{})
embeds_one(:attachment, AttachmentValidator)
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
index 721749de0..b3638cfc7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_fixes.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
+ alias Pleroma.Object.Containment
alias Pleroma.Web.ActivityPub.Utils
# based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults
@@ -11,12 +12,20 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
Utils.create_context(data["context"] || data["conversation"])
data
- |> Map.put_new("context", context)
- |> Map.put_new("context_id", context_id)
+ |> Map.put("context", context)
+ |> Map.put("context_id", context_id)
end
def fix_attribution(data) do
data
|> Map.put_new("actor", data["attributedTo"])
end
+
+ def fix_actor(data) do
+ actor = Containment.get_actor(data)
+
+ data
+ |> Map.put("actor", actor)
+ |> Map.put("attributedTo", actor)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
index b3dbeea57..422ee07be 100644
--- a/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/create_generic_validator.ex
@@ -10,9 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
+ alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset
- import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
@@ -75,14 +76,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
data
|> fix_context(meta)
|> fix_addressing(meta)
+ |> CommonFixes.fix_actor()
end
def validate_data(cng, meta \\ []) do
cng
|> validate_required([:actor, :type, :object])
|> validate_inclusion(:type, ["Create"])
- |> validate_actor_presence()
- |> validate_any_presence([:to, :cc])
+ |> CommonValidations.validate_actor_presence()
+ |> CommonValidations.validate_any_presence([:to, :cc])
|> validate_actors_match(meta)
|> validate_context_match(meta)
|> validate_object_nonexistence()
diff --git a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
index 07e4821a4..0b4c99dc0 100644
--- a/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/event_validator.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
+ alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@@ -39,8 +40,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
field(:attributedTo, ObjectValidators.ObjectID)
field(:published, ObjectValidators.DateTime)
- # TODO: Write type
- field(:emoji, :map, default: %{})
+ field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
@@ -74,6 +74,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
+ |> Transmogrifier.fix_emoji()
end
def changeset(struct, data) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex b/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
deleted file mode 100644
index 20e735619..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/note_validator.ex
+++ /dev/null
@@ -1,66 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
- use Ecto.Schema
-
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
-
- import Ecto.Changeset
-
- @primary_key false
-
- embedded_schema do
- field(:id, ObjectValidators.ObjectID, primary_key: true)
- field(:to, ObjectValidators.Recipients, default: [])
- field(:cc, ObjectValidators.Recipients, default: [])
- field(:bto, ObjectValidators.Recipients, default: [])
- field(:bcc, ObjectValidators.Recipients, default: [])
- # TODO: Write type
- field(:tag, {:array, :map}, default: [])
- field(:type, :string)
-
- field(:name, :string)
- field(:summary, :string)
- field(:content, :string)
-
- field(:context, :string)
- # short identifier for PleromaFE to group statuses by context
- field(:context_id, :integer)
-
- field(:actor, ObjectValidators.ObjectID)
- field(:attributedTo, ObjectValidators.ObjectID)
- field(:published, ObjectValidators.DateTime)
- # TODO: Write type
- field(:emoji, :map, default: %{})
- field(:sensitive, :boolean, default: false)
- # TODO: Write type
- field(:attachment, {:array, :map}, default: [])
- field(:replies_count, :integer, default: 0)
- field(:like_count, :integer, default: 0)
- field(:announcement_count, :integer, default: 0)
- field(:inReplyTo, ObjectValidators.ObjectID)
- field(:url, ObjectValidators.Uri)
-
- field(:likes, {:array, :string}, default: [])
- field(:announcements, {:array, :string}, default: [])
- end
-
- def cast_and_validate(data) do
- data
- |> cast_data()
- |> validate_data()
- end
-
- def cast_data(data) do
- %__MODULE__{}
- |> cast(data, __schema__(:fields))
- end
-
- def validate_data(data_cng) do
- data_cng
- |> validate_inclusion(:type, ["Note"])
- |> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
- end
-end
diff --git a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
index 712047424..9310485dc 100644
--- a/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/question_validator.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionOptionsValidator
+ alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset
@@ -35,8 +36,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
field(:attributedTo, ObjectValidators.ObjectID)
field(:summary, :string)
field(:published, ObjectValidators.DateTime)
- # TODO: Write type
- field(:emoji, :map, default: %{})
+ field(:emoji, ObjectValidators.Emoji, default: %{})
field(:sensitive, :boolean, default: false)
embeds_many(:attachment, AttachmentValidator)
field(:replies_count, :integer, default: 0)
@@ -47,8 +47,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
# short identifier for PleromaFE to group statuses by context
field(:context_id, :integer)
- field(:likes, {:array, :string}, default: [])
- field(:announcements, {:array, :string}, default: [])
+ field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
+ field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
@@ -85,6 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
+ |> Transmogrifier.fix_emoji()
|> fix_closed()
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex b/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
deleted file mode 100644
index 881030f38..000000000
--- a/lib/pleroma/web/activity_pub/object_validators/url_object_validator.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.UrlObjectValidator do
- use Ecto.Schema
-
- alias Pleroma.EctoType.ActivityPub.ObjectValidators
-
- import Ecto.Changeset
- @primary_key false
-
- embedded_schema do
- field(:type, :string)
- field(:href, ObjectValidators.Uri)
- field(:mediaType, :string, default: "application/octet-stream")
- end
-
- def changeset(struct, data) do
- struct
- |> cast(data, __schema__(:fields))
- |> validate_required([:type, :href, :mediaType])
- end
-end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index d88f7f3ee..a2930c1cd 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.FedSockets
require Pleroma.Constants
@@ -50,15 +51,35 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.debug("Federating #{id} to #{inbox}")
- uri = URI.parse(inbox)
+ case FedSockets.get_or_create_fed_socket(inbox) do
+ {:ok, fedsocket} ->
+ Logger.debug("publishing via fedsockets - #{inspect(inbox)}")
+ FedSockets.publish(fedsocket, json)
+ _ ->
+ Logger.debug("publishing via http - #{inspect(inbox)}")
+ http_publish(inbox, actor, json, params)
+ end
+ end
+
+ def publish_one(%{actor_id: actor_id} = params) do
+ actor = User.get_cached_by_id(actor_id)
+
+ params
+ |> Map.delete(:actor_id)
+ |> Map.put(:actor, actor)
+ |> publish_one()
+ end
+
+ defp http_publish(inbox, actor, json, params) do
+ uri = %{path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
date = Pleroma.Signature.signed_date()
signature =
Pleroma.Signature.sign(actor, %{
- "(request-target)": "post #{uri.path}",
+ "(request-target)": "post #{path}",
host: signature_host(uri),
"content-length": byte_size(json),
digest: digest,
@@ -89,15 +110,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end
end
- def publish_one(%{actor_id: actor_id} = params) do
- actor = User.get_cached_by_id(actor_id)
-
- params
- |> Map.delete(:actor_id)
- |> Map.put(:actor, actor)
- |> publish_one()
- end
-
defp signature_host(%URI{port: port, scheme: scheme, host: host}) do
if port == URI.default_port(scheme) do
host
@@ -230,9 +242,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
end)
end
- @doc """
- Publishes an activity to all relevant peers.
- """
+ # Publishes an activity to all relevant peers.
def publish(%User{} = actor, %Activity{} = activity) do
public = is_public?(activity)
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index b65710a94..6606e1780 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -30,12 +30,16 @@ defmodule Pleroma.Web.ActivityPub.Relay do
end
end
- @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
- def unfollow(target_instance) do
+ @spec unfollow(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
+ def unfollow(target_instance, opts \\ %{}) do
with %User{} = local_user <- get_actor(),
- {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
+ {:ok, target_user} <- fetch_target_user(target_instance, opts),
{:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
- User.unfollow(local_user, target_user)
+ case target_user.id do
+ nil -> User.update_following_count(local_user)
+ _ -> User.unfollow(local_user, target_user)
+ end
+
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity}
else
@@ -43,6 +47,14 @@ defmodule Pleroma.Web.ActivityPub.Relay do
end
end
+ defp fetch_target_user(ap_id, opts) do
+ case {opts[:force], User.get_or_fetch_by_ap_id(ap_id)} do
+ {_, {:ok, %User{} = user}} -> {:ok, user}
+ {true, _} -> {:ok, %User{ap_id: ap_id}}
+ {_, error} -> error
+ end
+ end
+
@spec publish(any()) :: {:ok, Activity.t()} | {:error, any()}
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
with %User{} = user <- get_actor(),
diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex
index a5e2323bd..0fff5faf2 100644
--- a/lib/pleroma/web/activity_pub/side_effects.ex
+++ b/lib/pleroma/web/activity_pub/side_effects.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.ActivityPub.SideEffects do
@moduledoc """
This module looks at an inserted object and executes the side effects that it
@@ -7,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
"""
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
- alias Pleroma.ActivityExpiration
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship
@@ -99,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
{_, {:ok, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
- if followed.local && !followed.locked do
+ if followed.local && !followed.is_locked do
{:ok, accept_data, _} = Builder.accept(followed, object)
{:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true)
end
@@ -188,10 +191,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
Object.increase_replies_count(in_reply_to)
end
- if expires_at = activity.data["expires_at"] do
- ActivityExpiration.create(activity, expires_at)
- end
-
BackgroundWorker.enqueue("fetch_data_for_activity", %{"activity_id" => activity.id})
meta =
@@ -307,6 +306,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
streamables =
[[actor, recipient], [recipient, actor]]
+ |> Enum.uniq()
|> Enum.map(fn [user, other_user] ->
if user.local do
{:ok, chat} = Chat.bump_or_create(user.id, other_user.ap_id)
@@ -341,7 +341,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
end
def handle_object_creation(%{"type" => objtype} = object, meta)
- when objtype in ~w[Audio Question Event] do
+ when objtype in ~w[Audio Video Question Event Article] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta}
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 63dd227c1..39c8f7e39 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -7,7 +7,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
A module to handle coding from internal to wire ActivityPub and back.
"""
alias Pleroma.Activity
- alias Pleroma.EarmarkRenderer
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Maps
alias Pleroma.Object
@@ -41,11 +40,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_in_reply_to(options)
|> fix_emoji
|> fix_tag
+ |> set_sensitive
|> fix_content_map
|> fix_addressing
|> fix_summary
|> fix_type(options)
- |> fix_content
end
def fix_summary(%{"summary" => nil} = object) do
@@ -168,7 +167,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id = prepare_in_reply_to(in_reply_to)
- object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(depth) do
@@ -176,9 +174,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
|> Map.put("inReplyTo", replied_object.data["id"])
- |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("context", replied_object.data["context"] || object["conversation"])
- |> Map.drop(["conversation"])
+ |> Map.drop(["conversation", "inReplyToAtomUri"])
else
e ->
Logger.warn("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
@@ -276,24 +273,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "url", url["href"])
end
- def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
- attachment =
- Enum.find(url, fn x ->
- media_type = x["mediaType"] || x["mimeType"] || ""
-
- is_map(x) and String.starts_with?(media_type, "video/")
- end)
-
- link_element =
- Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
-
- object
- |> Map.put("attachment", [attachment])
- |> Map.put("url", link_element["href"])
- end
-
- def fix_url(%{"type" => object_type, "url" => url} = object)
- when object_type != "Video" and is_list(url) do
+ def fix_url(%{"url" => url} = object) when is_list(url) do
first_element = Enum.at(url, 0)
url_string =
@@ -318,9 +298,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(mapping, name, data["icon"]["url"])
end)
- # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
- emoji = Map.merge(object["emoji"] || %{}, emoji)
-
Map.put(object, "emoji", emoji)
end
@@ -337,19 +314,21 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
tags =
tag
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
- |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
+ |> Enum.map(fn %{"name" => name} ->
+ name
+ |> String.slice(1..-1)
+ |> String.downcase()
+ end)
Map.put(object, "tag", tag ++ tags)
end
- def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
- combined = [tag, String.slice(hashtag, 1..-1)]
-
- Map.put(object, "tag", combined)
+ def fix_tag(%{"tag" => %{} = tag} = object) do
+ object
+ |> Map.put("tag", [tag])
+ |> fix_tag
end
- def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])
-
def fix_tag(object), do: object
# content map usually only has one language so this will do for now.
@@ -376,18 +355,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_type(object, _), do: object
- defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object)
- when is_binary(content) do
- html_content =
- content
- |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer})
- |> Pleroma.HTML.filter_tags()
-
- Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"})
- end
-
- defp fix_content(object), do: object
-
# Reduce the object list to find the reported user.
defp get_reported(objects) do
Enum.reduce_while(objects, nil, fn ap_id, _ ->
@@ -460,7 +427,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
options
)
- when objtype in ~w{Article Note Video Page} do
+ when objtype in ~w{Note Page} do
actor = Containment.get_actor(data)
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
@@ -551,13 +518,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def handle_incoming(
- %{"type" => "Create", "object" => %{"type" => objtype}} = data,
+ %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
_options
)
- when objtype in ~w{Question Answer ChatMessage Audio Event} do
+ when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
+ data = Map.put(data, "object", strip_internal_fields(data["object"]))
+
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+ nil <- Activity.get_create_by_object_ap_id(obj_id),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
+ else
+ %Activity{} = activity -> {:ok, activity}
+ e -> e
end
end
@@ -957,7 +930,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Map.put(object, "conversation", object["context"])
end
- def set_sensitive(%{"sensitive" => true} = object) do
+ def set_sensitive(%{"sensitive" => _} = object) do
object
end
@@ -1034,7 +1007,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def upgrade_user_from_ap_id(ap_id) do
with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
- {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
+ {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id, force_http: true),
{:ok, user} <- update_user(user, data) do
TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
{:ok, user}
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 3a4564912..c6dee61db 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -101,7 +101,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"name" => user.name,
"summary" => user.bio,
"url" => user.ap_id,
- "manuallyApprovesFollowers" => user.locked,
+ "manuallyApprovesFollowers" => user.is_locked,
"publicKey" => %{
"id" => "#{user.ap_id}#main-key",
"owner" => user.ap_id,
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 5c349bb7a..76bd54a42 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -44,29 +44,30 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
def is_list?(%{data: %{"listMessage" => _}}), do: true
def is_list?(_), do: false
- @spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
- def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
+ @spec visible_for_user?(Activity.t() | nil, User.t() | nil) :: boolean()
+ def visible_for_user?(%Activity{actor: ap_id}, %User{ap_id: ap_id}), do: true
def visible_for_user?(nil, _), do: false
- def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
+ def visible_for_user?(%Activity{data: %{"listMessage" => _}}, nil), do: false
- def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
+ def visible_for_user?(
+ %Activity{data: %{"listMessage" => list_ap_id}} = activity,
+ %User{} = user
+ ) do
user.ap_id in activity.data["to"] ||
list_ap_id
|> Pleroma.List.get_by_ap_id()
|> Pleroma.List.member?(user)
end
- def visible_for_user?(%{local: local} = activity, nil) do
- cfg_key = if local, do: :local, else: :remote
-
- if Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key),
+ def visible_for_user?(%Activity{} = activity, nil) do
+ if restrict_unauthenticated_access?(activity),
do: false,
else: is_public?(activity)
end
- def visible_for_user?(activity, user) do
+ def visible_for_user?(%Activity{} = activity, user) do
x = [user.ap_id | User.following(user)]
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || [])
is_public?(activity) || Enum.any?(x, &(&1 in y))
@@ -82,6 +83,26 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
result
end
+ def restrict_unauthenticated_access?(%Activity{local: local}) do
+ restrict_unauthenticated_access_to_activity?(local)
+ end
+
+ def restrict_unauthenticated_access?(%Object{} = object) do
+ object
+ |> Object.local?()
+ |> restrict_unauthenticated_access_to_activity?()
+ end
+
+ def restrict_unauthenticated_access?(%User{} = user) do
+ User.visible_for(user, _reading_user = nil)
+ end
+
+ defp restrict_unauthenticated_access_to_activity?(local?) when is_boolean(local?) do
+ cfg_key = if local?, do: :local, else: :remote
+
+ Pleroma.Config.restrict_unauthenticated_access?(:activities, cfg_key)
+ end
+
def get_visibility(object) do
to = object.data["to"] || []
cc = object.data["cc"] || []
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
index f5e4d49f9..bdd3e195d 100644
--- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Config
alias Pleroma.MFA
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -21,10 +20,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Web.AdminAPI.ModerationLogView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Router
- require Logger
-
@users_page_size 50
plug(
@@ -70,6 +68,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug(
OAuthScopesPlug,
+ %{scopes: ["read:chats"], admin: true}
+ when action in [:list_user_chats]
+ )
+
+ plug(
+ OAuthScopesPlug,
%{scopes: ["read"], admin: true}
when action in [
:list_log,
@@ -256,6 +260,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
+ def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
+ with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
+ chats =
+ Pleroma.Chat.for_user_query(user_id)
+ |> Pleroma.Repo.all()
+
+ conn
+ |> put_view(AdminAPI.ChatView)
+ |> render("index.json", chats: chats)
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname)
diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
new file mode 100644
index 000000000..af8ff8292
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ChatController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Activity
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.ModerationLog
+ alias Pleroma.Pagination
+ alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:chats"], admin: true} when action in [:show, :messages]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:chats"], admin: true} when action in [:delete_message]
+ )
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ChatOperation
+
+ def delete_message(%{assigns: %{user: user}} = conn, %{
+ message_id: message_id,
+ id: chat_id
+ }) do
+ with %MessageReference{object: %{data: %{"id" => object_ap_id}}} = cm_ref <-
+ MessageReference.get_by_id(message_id),
+ ^chat_id <- to_string(cm_ref.chat_id),
+ %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object_ap_id),
+ {:ok, _} <- CommonAPI.delete(activity_id, user) do
+ ModerationLog.insert_log(%{
+ action: "chat_message_delete",
+ actor: user,
+ subject_id: message_id
+ })
+
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("show.json", chat_message_reference: cm_ref)
+ else
+ _e ->
+ {:error, :could_not_delete}
+ end
+ end
+
+ def messages(conn, %{id: id} = params) do
+ with %Chat{} = chat <- Chat.get_by_id(id) do
+ cm_refs =
+ chat
+ |> MessageReference.for_chat_query()
+ |> Pagination.fetch_paginated(params)
+
+ conn
+ |> put_view(MessageReferenceView)
+ |> render("index.json", chat_message_references: cm_refs)
+ else
+ _ ->
+ conn
+ |> put_status(:not_found)
+ |> json(%{error: "not found"})
+ end
+ end
+
+ def show(conn, %{id: id}) do
+ with %Chat{} = chat <- Chat.get_by_id(id) do
+ conn
+ |> put_view(AdminAPI.ChatView)
+ |> render("show.json", chat: chat)
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
index 0df13007f..5d155af3d 100644
--- a/lib/pleroma/web/admin_api/controllers/config_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
alias Pleroma.Config
alias Pleroma.ConfigDB
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
new file mode 100644
index 000000000..37dbfeb72
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Web.InstanceDocument
+ alias Pleroma.Web.Plugs.InstanceStatic
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation
+
+ plug(OAuthScopesPlug, %{scopes: ["read"], admin: true} when action == :show)
+ plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action in [:update, :delete])
+
+ def show(conn, %{name: document_name}) do
+ with {:ok, url} <- InstanceDocument.get(document_name),
+ {:ok, content} <- File.read(InstanceStatic.file_path(url)) do
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, content)
+ end
+ end
+
+ def update(%{body_params: %{file: file}} = conn, %{name: document_name}) do
+ with {:ok, url} <- InstanceDocument.put(document_name, file.path) do
+ json(conn, %{"url" => url})
+ end
+ end
+
+ def delete(conn, %{name: document_name}) do
+ with :ok <- InstanceDocument.delete(document_name) do
+ json(conn, %{})
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
index 7d169b8d2..6a9b4038a 100644
--- a/lib/pleroma/web/admin_api/controllers/invite_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.AdminAPI.InviteController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Config
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.UserInviteToken
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
index 131e22d78..6d92e9f7f 100644
--- a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ApiSpec.Admin, as: Spec
alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
index dca23ea73..116a05a4d 100644
--- a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
@@ -7,8 +7,8 @@ defmodule Pleroma.Web.AdminAPI.OAuthAppController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
index 95d06dde7..611388447 100644
--- a/lib/pleroma/web/admin_api/controllers/relay_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
use Pleroma.Web, :controller
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
@@ -33,11 +33,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
with {:ok, _message} <- Relay.follow(target) do
- ModerationLog.insert_log(%{
- action: "relay_follow",
- actor: admin,
- target: target
- })
+ ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target})
json(conn, %{actor: target, followed_back: target in Relay.following()})
else
@@ -48,13 +44,9 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
end
end
- def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
- with {:ok, _message} <- Relay.unfollow(target) do
- ModerationLog.insert_log(%{
- action: "relay_unfollow",
- actor: admin,
- target: target
- })
+ def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do
+ with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do
+ ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target})
json(conn, target)
else
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
index 4c011e174..86da93893 100644
--- a/lib/pleroma/web/admin_api/controllers/report_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/report_controller.ex
@@ -9,12 +9,12 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
alias Pleroma.Activity
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ReportNote
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex
index bc48cc527..2bb437cfe 100644
--- a/lib/pleroma/web/admin_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex
@@ -7,10 +7,10 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
alias Pleroma.Activity
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/views/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex
index 9c477feab..bda7ea19c 100644
--- a/lib/pleroma/web/admin_api/views/account_view.ex
+++ b/lib/pleroma/web/admin_api/views/account_view.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
:fields,
:name,
:nickname,
- :locked,
+ :is_locked,
:no_rich_text,
:default_scope,
:hide_follows,
diff --git a/lib/pleroma/web/admin_api/views/chat_view.ex b/lib/pleroma/web/admin_api/views/chat_view.ex
new file mode 100644
index 000000000..847df1423
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/chat_view.ex
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.ChatView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.Chat
+ alias Pleroma.User
+ alias Pleroma.Web.MastodonAPI
+ alias Pleroma.Web.PleromaAPI
+
+ def render("index.json", %{chats: chats} = opts) do
+ render_many(chats, __MODULE__, "show.json", Map.delete(opts, :chats))
+ end
+
+ def render("show.json", %{chat: %Chat{user_id: user_id}} = opts) do
+ user = User.get_by_id(user_id)
+ sender = MastodonAPI.AccountView.render("show.json", user: user, skip_visibility_check: true)
+
+ serialized_chat = PleromaAPI.ChatView.render("show.json", opts)
+
+ serialized_chat
+ |> Map.put(:sender, sender)
+ |> Map.put(:receiver, serialized_chat[:account])
+ |> Map.delete(:account)
+ end
+
+ def render(view, opts), do: PleromaAPI.ChatView.render(view, opts)
+end
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index 773f798fe..535556370 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -52,7 +52,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
end
def render("index_notes.json", %{notes: notes}) when is_list(notes) do
- Enum.map(notes, &render(__MODULE__, "show_note.json", &1))
+ Enum.map(notes, &render(__MODULE__, "show_note.json", Map.from_struct(&1)))
end
def render("index_notes.json", _), do: []
diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex
index 500800be2..6042a22b6 100644
--- a/lib/pleroma/web/admin_api/views/status_view.ex
+++ b/lib/pleroma/web/admin_api/views/status_view.ex
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
require Pleroma.Constants
alias Pleroma.Web.AdminAPI
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI
defdelegate merge_account_views(user), to: AdminAPI.AccountView
@@ -17,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
end
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
- user = MastodonAPI.StatusView.get_user(activity.data["actor"])
+ user = CommonAPI.get_user(activity.data["actor"])
MastodonAPI.StatusView.render("show.json", opts)
|> Map.merge(%{account: merge_account_views(user)})
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index 79fd5f871..93a5273e3 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -13,10 +13,15 @@ defmodule Pleroma.Web.ApiSpec do
@impl OpenApi
def spec do
%OpenApi{
- servers: [
- # Populate the Server info from a phoenix endpoint
- OpenApiSpex.Server.from_endpoint(Endpoint)
- ],
+ servers:
+ if Phoenix.Endpoint.server?(:pleroma, Endpoint) do
+ [
+ # Populate the Server info from a phoenix endpoint
+ OpenApiSpex.Server.from_endpoint(Endpoint)
+ ]
+ else
+ []
+ end,
info: %OpenApiSpex.Info{
title: "Pleroma",
description: Application.spec(:pleroma, :description) |> to_string(),
diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex
index fbfc27d6f..6d1a7ebbc 100644
--- a/lib/pleroma/web/api_spec/cast_and_validate.ex
+++ b/lib/pleroma/web/api_spec/cast_and_validate.ex
@@ -115,6 +115,10 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
%{reason: :unexpected_field, name: name, path: [name]}, params ->
Map.delete(params, name)
+ # Filter out empty params
+ %{reason: :invalid_type, path: [name_atom], value: ""}, params ->
+ Map.delete(params, to_string(name_atom))
+
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
update_in(params, path, &List.delete(&1, value))
diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index 2a7f1a706..34de2ed57 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -72,7 +72,11 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
end
def empty_array_response do
- Operation.response("Empty array", "application/json", %Schema{type: :array, example: []})
+ Operation.response("Empty array", "application/json", %Schema{
+ type: :array,
+ items: %Schema{type: :object, example: %{}},
+ example: []
+ })
end
def no_content_response do
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index aaebc9b5c..d90ddb787 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -372,6 +372,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
tags: ["accounts"],
summary: "Identity proofs",
operationId: "AccountController.identity_proofs",
+ # Validators complains about unused path params otherwise
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
+ ],
description: "Not implemented",
responses: %{
200 => empty_array_response()
@@ -469,7 +473,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
identifier: %Schema{type: :string},
message: %Schema{type: :string}
},
- required: [],
# Note: example of successful registration with failed login response:
# example: %{
# "identifier" => "missing_confirmed_email",
@@ -530,7 +533,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
nullable: true,
oneOf: [
%Schema{type: :array, items: attribute_field()},
- %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}}
+ %Schema{type: :object, additionalProperties: attribute_field()}
]
},
# NOTE: `source` field is not supported
diff --git a/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
new file mode 100644
index 000000000..d3e5dfc1c
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/chat_operation.ex
@@ -0,0 +1,96 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.ChatOperation do
+ alias OpenApiSpex.Operation
+ alias Pleroma.Web.ApiSpec.Schemas.Chat
+ alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def delete_message_operation do
+ %Operation{
+ tags: ["admin", "chat"],
+ summary: "Delete an individual chat message",
+ operationId: "AdminAPI.ChatController.delete_message",
+ parameters: [
+ Operation.parameter(:id, :path, :string, "The ID of the Chat"),
+ Operation.parameter(:message_id, :path, :string, "The ID of the message")
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The deleted ChatMessage",
+ "application/json",
+ ChatMessage
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["write:chats"]
+ }
+ ]
+ }
+ end
+
+ def messages_operation do
+ %Operation{
+ tags: ["admin", "chat"],
+ summary: "Get the most recent messages of the chat",
+ operationId: "AdminAPI.ChatController.messages",
+ parameters:
+ [Operation.parameter(:id, :path, :string, "The ID of the Chat")] ++
+ pagination_params(),
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The messages in the chat",
+ "application/json",
+ Pleroma.Web.ApiSpec.ChatOperation.chat_messages_response()
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["read:chats"]
+ }
+ ]
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["chat"],
+ summary: "Create a chat",
+ operationId: "AdminAPI.ChatController.show",
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "The id of the chat",
+ required: true,
+ example: "1234"
+ )
+ ],
+ responses: %{
+ 200 =>
+ Operation.response(
+ "The existing chat",
+ "application/json",
+ Chat
+ )
+ },
+ security: [
+ %{
+ "oAuth" => ["read"]
+ }
+ ]
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex
new file mode 100644
index 000000000..a120ff4e8
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/instance_document_operation.ex
@@ -0,0 +1,115 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.InstanceDocumentOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Helpers
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["Admin", "InstanceDocument"],
+ summary: "Get the instance document",
+ operationId: "AdminAPI.InstanceDocumentController.show",
+ security: [%{"oAuth" => ["read"]}],
+ parameters: [
+ Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
+ required: true
+ )
+ | Helpers.admin_api_params()
+ ],
+ responses: %{
+ 200 => document_content(),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def update_operation do
+ %Operation{
+ tags: ["Admin", "InstanceDocument"],
+ summary: "Update the instance document",
+ operationId: "AdminAPI.InstanceDocumentController.update",
+ security: [%{"oAuth" => ["write"]}],
+ requestBody: Helpers.request_body("Parameters", update_request()),
+ parameters: [
+ Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
+ required: true
+ )
+ | Helpers.admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("InstanceDocument", "application/json", instance_document()),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp update_request do
+ %Schema{
+ title: "UpdateRequest",
+ description: "POST body for uploading the file",
+ type: :object,
+ required: [:file],
+ properties: %{
+ file: %Schema{
+ type: :string,
+ format: :binary,
+ description: "The file to be uploaded, using multipart form data."
+ }
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Admin", "InstanceDocument"],
+ summary: "Get the instance document",
+ operationId: "AdminAPI.InstanceDocumentController.delete",
+ security: [%{"oAuth" => ["write"]}],
+ parameters: [
+ Operation.parameter(:name, :path, %Schema{type: :string}, "The document name",
+ required: true
+ )
+ | Helpers.admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("InstanceDocument", "application/json", instance_document()),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp instance_document do
+ %Schema{
+ title: "InstanceDocument",
+ type: :object,
+ properties: %{
+ url: %Schema{type: :string}
+ },
+ example: %{
+ "url" => "https://example.com/static/terms-of-service.html"
+ }
+ }
+ end
+
+ defp document_content do
+ Operation.response("InstanceDocumentContent", "text/html", %Schema{
+ type: :string,
+ example: "<h1>Instance panel</h1>"
+ })
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
index a75f3e622..a75f3e622 100644
--- a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
diff --git a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
index e06b2d164..f754bb9f5 100644
--- a/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
operationId: "AdminAPI.RelayController.unfollow",
security: [%{"oAuth" => ["write:follows"]}],
parameters: admin_api_params(),
- requestBody: request_body("Parameters", relay_url()),
+ requestBody: request_body("Parameters", relay_unfollow()),
responses: %{
200 =>
Operation.response("Status", "application/json", %Schema{
@@ -91,4 +91,14 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
}
}
end
+
+ defp relay_unfollow do
+ %Schema{
+ type: :object,
+ properties: %{
+ relay_url: %Schema{type: :string, format: :uri},
+ force: %Schema{type: :boolean, default: false}
+ }
+ }
+ end
end
diff --git a/lib/pleroma/web/api_spec/operations/chat_operation.ex b/lib/pleroma/web/api_spec/operations/chat_operation.ex
index 56554d5b4..0dcfdb354 100644
--- a/lib/pleroma/web/api_spec/operations/chat_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/chat_operation.ex
@@ -158,7 +158,8 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
"The messages in the chat",
"application/json",
chat_messages_response()
- )
+ ),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
},
security: [
%{
diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
index 2f812ac77..5ff263ceb 100644
--- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
@@ -69,7 +69,7 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
type: :object,
properties: %{
category: %Schema{type: :string},
- tags: %Schema{type: :array}
+ tags: %Schema{type: :array, items: %Schema{type: :string}}
}
}
],
diff --git a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
index 1a49fece0..745d41f88 100644
--- a/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/emoji_reaction_operation.ex
@@ -23,7 +23,7 @@ defmodule Pleroma.Web.ApiSpec.EmojiReactionOperation do
parameters: [
Operation.parameter(:id, :path, FlakeID, "Status ID", required: true),
Operation.parameter(:emoji, :path, :string, "Filter by a single unicode emoji",
- required: false
+ required: nil
)
],
security: [%{"oAuth" => ["read:statuses"]}],
diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex
index c88ed5dd0..f6e73968a 100644
--- a/lib/pleroma/web/api_spec/operations/list_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/list_operation.ex
@@ -114,7 +114,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
description: "Add accounts to the given list.",
operationId: "ListController.add_to_list",
parameters: [id_param()],
- requestBody: add_remove_accounts_request(),
+ requestBody: add_remove_accounts_request(true),
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
@@ -127,8 +127,16 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
tags: ["Lists"],
summary: "Remove accounts from list",
operationId: "ListController.remove_from_list",
- parameters: [id_param()],
- requestBody: add_remove_accounts_request(),
+ parameters: [
+ id_param(),
+ Operation.parameter(
+ :account_ids,
+ :query,
+ %Schema{type: :array, items: %Schema{type: :string}},
+ "Array of account IDs"
+ )
+ ],
+ requestBody: add_remove_accounts_request(false),
security: [%{"oAuth" => ["write:lists"]}],
responses: %{
200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
@@ -171,7 +179,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
)
end
- defp add_remove_accounts_request do
+ defp add_remove_accounts_request(required) when is_boolean(required) do
request_body(
"Parameters",
%Schema{
@@ -179,10 +187,9 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
type: :object,
properties: %{
account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID}
- },
- required: [:account_ids]
+ }
},
- required: true
+ required: required
)
end
end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex
new file mode 100644
index 000000000..a56641426
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex
@@ -0,0 +1,139 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["Emoji Packs"],
+ summary: "Add new file to the pack",
+ operationId: "PleromaAPI.EmojiPackController.add_file",
+ security: [%{"oAuth" => ["write"]}],
+ requestBody: request_body("Parameters", create_request(), required: true),
+ parameters: [name_param()],
+ responses: %{
+ 200 => Operation.response("Files Object", "application/json", files_object()),
+ 422 => Operation.response("Unprocessable Entity", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 409 => Operation.response("Conflict", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp create_request do
+ %Schema{
+ type: :object,
+ required: [:file],
+ properties: %{
+ file: %Schema{
+ description:
+ "File needs to be uploaded with the multipart request or link to remote file",
+ anyOf: [
+ %Schema{type: :string, format: :binary},
+ %Schema{type: :string, format: :uri}
+ ]
+ },
+ shortcode: %Schema{
+ type: :string,
+ description:
+ "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename."
+ },
+ filename: %Schema{
+ type: :string,
+ description:
+ "New emoji file name. If not specified will be taken from original filename."
+ }
+ }
+ }
+ end
+
+ def update_operation do
+ %Operation{
+ tags: ["Emoji Packs"],
+ summary: "Add new file to the pack",
+ operationId: "PleromaAPI.EmojiPackController.update_file",
+ security: [%{"oAuth" => ["write"]}],
+ requestBody: request_body("Parameters", update_request(), required: true),
+ parameters: [name_param()],
+ responses: %{
+ 200 => Operation.response("Files Object", "application/json", files_object()),
+ 404 => Operation.response("Not Found", "application/json", ApiError),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 409 => Operation.response("Conflict", "application/json", ApiError),
+ 422 => Operation.response("Unprocessable Entity", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp update_request do
+ %Schema{
+ type: :object,
+ required: [:shortcode, :new_shortcode, :new_filename],
+ properties: %{
+ shortcode: %Schema{
+ type: :string,
+ description: "Emoji file shortcode"
+ },
+ new_shortcode: %Schema{
+ type: :string,
+ description: "New emoji file shortcode"
+ },
+ new_filename: %Schema{
+ type: :string,
+ description: "New filename for emoji file"
+ },
+ force: %Schema{
+ type: :boolean,
+ description: "With true value to overwrite existing emoji with new shortcode",
+ default: false
+ }
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Emoji Packs"],
+ summary: "Delete emoji file from pack",
+ operationId: "PleromaAPI.EmojiPackController.delete_file",
+ security: [%{"oAuth" => ["write"]}],
+ parameters: [
+ name_param(),
+ Operation.parameter(:shortcode, :query, :string, "File shortcode",
+ example: "cofe",
+ required: true
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Files Object", "application/json", files_object()),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError),
+ 422 => Operation.response("Unprocessable Entity", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp name_param do
+ Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true)
+ end
+
+ defp files_object do
+ %Schema{
+ type: :object,
+ additionalProperties: %Schema{type: :string},
+ description: "Object with emoji names as keys and filenames as values"
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
index b2b4f8713..79f52dcb3 100644
--- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex
@@ -19,7 +19,21 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
tags: ["Emoji Packs"],
summary: "Make request to another instance for emoji packs list",
security: [%{"oAuth" => ["write"]}],
- parameters: [url_param()],
+ parameters: [
+ url_param(),
+ Operation.parameter(
+ :page,
+ :query,
+ %Schema{type: :integer, default: 1},
+ "Page"
+ ),
+ Operation.parameter(
+ :page_size,
+ :query,
+ %Schema{type: :integer, default: 30},
+ "Number of emoji to return"
+ )
+ ],
operationId: "PleromaAPI.EmojiPackController.remote",
responses: %{
200 => emoji_packs_response(),
@@ -175,111 +189,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
}
end
- def add_file_operation do
- %Operation{
- tags: ["Emoji Packs"],
- summary: "Add new file to the pack",
- operationId: "PleromaAPI.EmojiPackController.add_file",
- security: [%{"oAuth" => ["write"]}],
- requestBody: request_body("Parameters", add_file_request(), required: true),
- parameters: [name_param()],
- responses: %{
- 200 => Operation.response("Files Object", "application/json", files_object()),
- 400 => Operation.response("Bad Request", "application/json", ApiError),
- 409 => Operation.response("Conflict", "application/json", ApiError)
- }
- }
- end
-
- defp add_file_request do
- %Schema{
- type: :object,
- required: [:file],
- properties: %{
- file: %Schema{
- description:
- "File needs to be uploaded with the multipart request or link to remote file",
- anyOf: [
- %Schema{type: :string, format: :binary},
- %Schema{type: :string, format: :uri}
- ]
- },
- shortcode: %Schema{
- type: :string,
- description:
- "Shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename."
- },
- filename: %Schema{
- type: :string,
- description:
- "New emoji file name. If not specified will be taken from original filename."
- }
- }
- }
- end
-
- def update_file_operation do
- %Operation{
- tags: ["Emoji Packs"],
- summary: "Add new file to the pack",
- operationId: "PleromaAPI.EmojiPackController.update_file",
- security: [%{"oAuth" => ["write"]}],
- requestBody: request_body("Parameters", update_file_request(), required: true),
- parameters: [name_param()],
- responses: %{
- 200 => Operation.response("Files Object", "application/json", files_object()),
- 400 => Operation.response("Bad Request", "application/json", ApiError),
- 409 => Operation.response("Conflict", "application/json", ApiError)
- }
- }
- end
-
- defp update_file_request do
- %Schema{
- type: :object,
- required: [:shortcode, :new_shortcode, :new_filename],
- properties: %{
- shortcode: %Schema{
- type: :string,
- description: "Emoji file shortcode"
- },
- new_shortcode: %Schema{
- type: :string,
- description: "New emoji file shortcode"
- },
- new_filename: %Schema{
- type: :string,
- description: "New filename for emoji file"
- },
- force: %Schema{
- type: :boolean,
- description: "With true value to overwrite existing emoji with new shortcode",
- default: false
- }
- }
- }
- end
-
- def delete_file_operation do
- %Operation{
- tags: ["Emoji Packs"],
- summary: "Delete emoji file from pack",
- operationId: "PleromaAPI.EmojiPackController.delete_file",
- security: [%{"oAuth" => ["write"]}],
- parameters: [
- name_param(),
- Operation.parameter(:shortcode, :query, :string, "File shortcode",
- example: "cofe",
- required: true
- )
- ],
- responses: %{
- 200 => Operation.response("Files Object", "application/json", files_object()),
- 400 => Operation.response("Bad Request", "application/json", ApiError)
- }
- }
- end
-
def import_from_filesystem_operation do
%Operation{
tags: ["Emoji Packs"],
@@ -297,7 +206,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do
end
defp name_param do
- Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true)
+ Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true)
end
defp url_param do
diff --git a/lib/pleroma/web/api_spec/operations/user_import_operation.ex b/lib/pleroma/web/api_spec/operations/user_import_operation.ex
new file mode 100644
index 000000000..a50314fb7
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/user_import_operation.ex
@@ -0,0 +1,80 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.UserImportOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ @spec open_api_operation(atom) :: Operation.t()
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def follow_operation do
+ %Operation{
+ tags: ["follow_import"],
+ summary: "Imports your follows.",
+ operationId: "UserImportController.follow",
+ requestBody: request_body("Parameters", import_request(), required: true),
+ responses: %{
+ 200 => ok_response(),
+ 500 => Operation.response("Error", "application/json", ApiError)
+ },
+ security: [%{"oAuth" => ["write:follow"]}]
+ }
+ end
+
+ def blocks_operation do
+ %Operation{
+ tags: ["blocks_import"],
+ summary: "Imports your blocks.",
+ operationId: "UserImportController.blocks",
+ requestBody: request_body("Parameters", import_request(), required: true),
+ responses: %{
+ 200 => ok_response(),
+ 500 => Operation.response("Error", "application/json", ApiError)
+ },
+ security: [%{"oAuth" => ["write:blocks"]}]
+ }
+ end
+
+ def mutes_operation do
+ %Operation{
+ tags: ["mutes_import"],
+ summary: "Imports your mutes.",
+ operationId: "UserImportController.mutes",
+ requestBody: request_body("Parameters", import_request(), required: true),
+ responses: %{
+ 200 => ok_response(),
+ 500 => Operation.response("Error", "application/json", ApiError)
+ },
+ security: [%{"oAuth" => ["write:mutes"]}]
+ }
+ end
+
+ defp import_request do
+ %Schema{
+ type: :object,
+ required: [:list],
+ properties: %{
+ list: %Schema{
+ description:
+ "STRING or FILE containing a whitespace-separated list of accounts to import.",
+ anyOf: [
+ %Schema{type: :string, format: :binary},
+ %Schema{type: :string}
+ ]
+ }
+ }
+ }
+ end
+
+ defp ok_response do
+ Operation.response("Ok", "application/json", %Schema{type: :string, example: "ok"})
+ end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/chat.ex b/lib/pleroma/web/api_spec/schemas/chat.ex
index b4986b734..65f908e33 100644
--- a/lib/pleroma/web/api_spec/schemas/chat.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat.ex
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Chat do
"fields" => []
},
"statuses_count" => 1,
- "locked" => false,
+ "is_locked" => false,
"created_at" => "2020-04-16T13:40:15.000Z",
"display_name" => "lain",
"fields" => [],
diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex
index bbf2a4427..9d2799618 100644
--- a/lib/pleroma/web/api_spec/schemas/chat_message.ex
+++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Emoji
require OpenApiSpex
@@ -18,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do
chat_id: %Schema{type: :string},
content: %Schema{type: :string, nullable: true},
created_at: %Schema{type: :string, format: :"date-time"},
- emojis: %Schema{type: :array},
+ emojis: %Schema{type: :array, items: Emoji},
attachment: %Schema{type: :object, nullable: true},
card: %Schema{
type: :object,
diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex
index 0520d0848..addefa9d3 100644
--- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex
+++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex
@@ -27,9 +27,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do
media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}},
sensitive: %Schema{type: :boolean, nullable: true},
spoiler_text: %Schema{type: :string, nullable: true},
- visibility: %Schema{type: VisibilityScope, nullable: true},
+ visibility: %Schema{allOf: [VisibilityScope], nullable: true},
scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true},
- poll: %Schema{type: Poll, nullable: true},
+ poll: %Schema{allOf: [Poll], nullable: true},
in_reply_to_id: %Schema{type: :string, nullable: true}
}
}
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index 947e42890..e6890df2d 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -252,7 +252,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"header" => "http://localhost:4001/images/banner.png",
"header_static" => "http://localhost:4001/images/banner.png",
"id" => "9toJCsKN7SmSf3aj5c",
- "locked" => false,
+ "is_locked" => false,
"note" => "Tester Number 6",
"pleroma" => %{
"background_image" => nil,
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 200ca03dc..d6d2a8d06 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
import Pleroma.Web.Auth.Authenticator,
only: [fetch_credentials: 1, fetch_user: 1]
@@ -68,7 +68,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
nickname = value([registration_attrs["nickname"], Registration.nickname(registration)])
email = value([registration_attrs["email"], Registration.email(registration)])
name = value([registration_attrs["name"], Registration.name(registration)]) || nickname
- bio = value([registration_attrs["bio"], Registration.description(registration)])
+ bio = value([registration_attrs["bio"], Registration.description(registration)]) || ""
random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
diff --git a/lib/pleroma/web/auth/totp_authenticator.ex b/lib/pleroma/web/auth/totp_authenticator.ex
index 1794e407c..edc9871ea 100644
--- a/lib/pleroma/web/auth/totp_authenticator.ex
+++ b/lib/pleroma/web/auth/totp_authenticator.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.Auth.TOTPAuthenticator do
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
@doc "Verify code or check backup code."
@spec verify(String.t(), User.t()) ::
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api.ex
index 3c7b9e794..60a50b027 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -4,7 +4,6 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
@@ -384,9 +383,9 @@ defmodule Pleroma.Web.CommonAPI do
def check_expiry_date({:ok, nil} = res), do: res
def check_expiry_date({:ok, in_seconds}) do
- expiry = NaiveDateTime.utc_now() |> NaiveDateTime.add(in_seconds)
+ expiry = DateTime.add(DateTime.utc_now(), in_seconds)
- if ActivityExpiration.expires_late_enough?(expiry) do
+ if Pleroma.Workers.PurgeExpiredActivity.expires_late_enough?(expiry) do
{:ok, expiry}
else
{:error, "Expiry date is too soon"}
@@ -455,7 +454,8 @@ defmodule Pleroma.Web.CommonAPI do
end
def add_mute(user, activity) do
- with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
+ with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
+ _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
{:ok, activity}
else
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
@@ -553,4 +553,21 @@ defmodule Pleroma.Web.CommonAPI do
def show_reblogs(%User{} = user, %User{} = target) do
UserRelationship.delete_reblog_mute(user, target)
end
+
+ def get_user(ap_id, fake_record_fallback \\ true) do
+ cond do
+ user = User.get_cached_by_ap_id(ap_id) ->
+ user
+
+ user = User.get_by_guessed_nickname(ap_id) ->
+ user
+
+ fake_record_fallback ->
+ # TODO: refactor (fake records is never a good idea)
+ User.error_user(ap_id)
+
+ true ->
+ nil
+ end
+ end
end
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
index f849b2e01..548f76609 100644
--- a/lib/pleroma/web/common_api/activity_draft.ex
+++ b/lib/pleroma/web/common_api/activity_draft.ex
@@ -202,7 +202,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
additional =
case draft.expires_at do
- %NaiveDateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
+ %DateTime{} = expires_at -> Map.put(additional, "expires_at", expires_at)
_ -> additional
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 9d7b24eb2..3b71adf0e 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -12,12 +12,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.Plugs.AuthenticationPlug
require Logger
require Pleroma.Constants
@@ -274,7 +274,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def format_input(text, format, options \\ [])
@doc """
- Formatting text to plain text.
+ Formatting text to plain text, BBCode, HTML, or Markdown
"""
def format_input(text, "text/plain", options) do
text
@@ -285,9 +285,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end).()
end
- @doc """
- Formatting text as BBCode.
- """
def format_input(text, "text/bbcode", options) do
text
|> String.replace(~r/\r/, "")
@@ -297,18 +294,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.linkify(options)
end
- @doc """
- Formatting text to html.
- """
def format_input(text, "text/html", options) do
text
|> Formatter.html_escape("text/html")
|> Formatter.linkify(options)
end
- @doc """
- Formatting text to markdown.
- """
def format_input(text, "text/markdown", options) do
text
|> Formatter.mentions_escape(options)
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 6445966e0..69188a882 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -48,13 +48,13 @@ defmodule Pleroma.Web.ControllerHelper do
defp param_to_integer(_, default), do: default
- def add_link_headers(conn, activities, extra_params \\ %{})
+ def add_link_headers(conn, entries, extra_params \\ %{})
- def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _activities, _extra_params),
+ def add_link_headers(%{assigns: %{skip_link_headers: true}} = conn, _entries, _extra_params),
do: conn
- def add_link_headers(conn, activities, extra_params) do
- case get_pagination_fields(conn, activities, extra_params) do
+ def add_link_headers(conn, entries, extra_params) do
+ case get_pagination_fields(conn, entries, extra_params) do
%{"next" => next_url, "prev" => prev_url} ->
put_resp_header(conn, "link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"")
@@ -78,19 +78,15 @@ defmodule Pleroma.Web.ControllerHelper do
}
end
- def get_pagination_fields(conn, activities, extra_params \\ %{}) do
- case List.last(activities) do
+ def get_pagination_fields(conn, entries, extra_params \\ %{}) do
+ case List.last(entries) do
%{pagination_id: max_id} when not is_nil(max_id) ->
- %{pagination_id: min_id} =
- activities
- |> List.first()
+ %{pagination_id: min_id} = List.first(entries)
build_pagination_fields(conn, min_id, max_id, extra_params)
%{id: max_id} ->
- %{id: min_id} =
- activities
- |> List.first()
+ %{id: min_id} = List.first(entries)
build_pagination_fields(conn, min_id, max_id, extra_params)
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 8b153763d..f26542e88 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -7,19 +7,23 @@ defmodule Pleroma.Web.Endpoint do
require Pleroma.Constants
+ alias Pleroma.Config
+
socket("/socket", Pleroma.Web.UserSocket)
- plug(Pleroma.Plugs.SetLocalePlug)
+ plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
+
+ plug(Pleroma.Web.Plugs.SetLocalePlug)
plug(CORSPlug)
- plug(Pleroma.Plugs.HTTPSecurityPlug)
- plug(Pleroma.Plugs.UploadedMedia)
+ plug(Pleroma.Web.Plugs.HTTPSecurityPlug)
+ plug(Pleroma.Web.Plugs.UploadedMedia)
@static_cache_control "public, no-cache"
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
# Cache-control headers are duplicated in case we turn off etags in the future
- plug(Pleroma.Plugs.InstanceStatic,
+ plug(Pleroma.Web.Plugs.InstanceStatic,
at: "/",
gzip: true,
cache_control_for_etags: @static_cache_control,
@@ -29,7 +33,7 @@ defmodule Pleroma.Web.Endpoint do
)
# Careful! No `only` restriction here, as we don't know what frontends contain.
- plug(Pleroma.Plugs.FrontendStatic,
+ plug(Pleroma.Web.Plugs.FrontendStatic,
at: "/",
frontend_type: :primary,
gzip: true,
@@ -41,7 +45,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
- plug(Pleroma.Plugs.FrontendStatic,
+ plug(Pleroma.Web.Plugs.FrontendStatic,
at: "/pleroma/admin",
frontend_type: :admin,
gzip: true,
@@ -79,26 +83,26 @@ defmodule Pleroma.Web.Endpoint do
plug(Phoenix.CodeReloader)
end
- plug(Pleroma.Plugs.TrailingFormatPlug)
+ plug(Pleroma.Web.Plugs.TrailingFormatPlug)
plug(Plug.RequestId)
plug(Plug.Logger, log: :debug)
plug(Plug.Parsers,
parsers: [
:urlencoded,
- {:multipart, length: {Pleroma.Config, :get, [[:instance, :upload_limit]]}},
+ {:multipart, length: {Config, :get, [[:instance, :upload_limit]]}},
:json
],
pass: ["*/*"],
json_decoder: Jason,
- length: Pleroma.Config.get([:instance, :upload_limit]),
+ length: Config.get([:instance, :upload_limit]),
body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
)
plug(Plug.MethodOverride)
plug(Plug.Head)
- secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
+ secure_cookies = Config.get([__MODULE__, :secure_cookie_flag])
cookie_name =
if secure_cookies,
@@ -106,7 +110,7 @@ defmodule Pleroma.Web.Endpoint do
else: "pleroma_key"
extra =
- Pleroma.Config.get([__MODULE__, :extra_cookie_attrs])
+ Config.get([__MODULE__, :extra_cookie_attrs])
|> Enum.join(";")
# The session will be stored in the cookie and signed,
@@ -116,13 +120,13 @@ defmodule Pleroma.Web.Endpoint do
Plug.Session,
store: :cookie,
key: cookie_name,
- signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
+ signing_salt: Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
http_only: true,
secure: secure_cookies,
extra: extra
)
- plug(Pleroma.Plugs.RemoteIp)
+ plug(Pleroma.Web.Plugs.RemoteIp)
defmodule Instrumenter do
use Prometheus.PhoenixInstrumenter
@@ -136,8 +140,34 @@ defmodule Pleroma.Web.Endpoint do
use Prometheus.PlugExporter
end
+ defmodule MetricsExporterCaller do
+ @behaviour Plug
+
+ def init(opts), do: opts
+
+ def call(conn, opts) do
+ prometheus_config = Application.get_env(:prometheus, MetricsExporter, [])
+ ip_whitelist = List.wrap(prometheus_config[:ip_whitelist])
+
+ cond do
+ !prometheus_config[:enabled] ->
+ conn
+
+ ip_whitelist != [] and
+ !Enum.find(ip_whitelist, fn ip ->
+ Pleroma.Helpers.InetHelper.parse_address(ip) == {:ok, conn.remote_ip}
+ end) ->
+ conn
+
+ true ->
+ MetricsExporter.call(conn, opts)
+ end
+ end
+ end
+
plug(PipelineInstrumenter)
- plug(MetricsExporter)
+
+ plug(MetricsExporterCaller)
plug(Pleroma.Web.Router)
diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex
index 431ad5485..6f759d559 100644
--- a/lib/pleroma/web/fallback_redirect_controller.ex
+++ b/lib/pleroma/web/fallback/redirect_controller.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Fallback.RedirectController do
+defmodule Pleroma.Web.Fallback.RedirectController do
use Pleroma.Web, :controller
require Logger
@@ -75,7 +75,7 @@ defmodule Fallback.RedirectController do
end
defp index_file_path do
- Pleroma.Plugs.InstanceStatic.file_path("index.html")
+ Pleroma.Web.Plugs.InstanceStatic.file_path("index.html")
end
defp build_tags(conn, params) do
diff --git a/lib/pleroma/web/fed_sockets.ex b/lib/pleroma/web/fed_sockets.ex
new file mode 100644
index 000000000..1fd5899c8
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets.ex
@@ -0,0 +1,185 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets do
+ @moduledoc """
+ This documents the FedSockets framework. A framework for federating
+ ActivityPub objects between servers via persistant WebSocket connections.
+
+ FedSockets allow servers to authenticate on first contact and maintain that
+ connection, eliminating the need to authenticate every time data needs to be shared.
+
+ ## Protocol
+ FedSockets currently support 2 types of data transfer:
+ * `publish` method which doesn't require a response
+ * `fetch` method requires a response be sent
+
+ ### Publish
+ The publish operation sends a json encoded map of the shape:
+ %{action: :publish, data: json}
+ and accepts (but does not require) a reply of form:
+ %{"action" => "publish_reply"}
+
+ The outgoing params represent
+ * data: ActivityPub object encoded into json
+
+
+ ### Fetch
+ The fetch operation sends a json encoded map of the shape:
+ %{action: :fetch, data: id, uuid: fetch_uuid}
+ and requires a reply of form:
+ %{"action" => "fetch_reply", "uuid" => uuid, "data" => data}
+
+ The outgoing params represent
+ * id: an ActivityPub object URI
+ * uuid: a unique uuid generated by the sender
+
+ The reply params represent
+ * data: an ActivityPub object encoded into json
+ * uuid: the uuid sent along with the fetch request
+
+ ## Examples
+ Clients of FedSocket transfers shouldn't need to use any of the functions outside of this module.
+
+ A typical publish operation can be performed through the following code, and a fetch operation in a similar manner.
+
+ case FedSockets.get_or_create_fed_socket(inbox) do
+ {:ok, fedsocket} ->
+ FedSockets.publish(fedsocket, json)
+
+ _ ->
+ alternative_publish(inbox, actor, json, params)
+ end
+
+ ## Configuration
+ FedSockets have the following config settings
+
+ config :pleroma, :fed_sockets,
+ enabled: true,
+ ping_interval: :timer.seconds(15),
+ connection_duration: :timer.hours(1),
+ rejection_duration: :timer.hours(1),
+ fed_socket_fetches: [
+ default: 12_000,
+ interval: 3_000,
+ lazy: false
+ ]
+ * enabled - turn FedSockets on or off with this flag. Can be toggled at runtime.
+ * connection_duration - How long a FedSocket can sit idle before it's culled.
+ * rejection_duration - After failing to make a FedSocket connection a host will be excluded
+ from further connections for this amount of time
+ * fed_socket_fetches - Use these parameters to pass options to the Cachex queue backing the FetchRegistry
+ * fed_socket_rejections - Use these parameters to pass options to the Cachex queue backing the FedRegistry
+
+ Cachex options are
+ * default: the minimum amount of time a fetch can wait before it times out.
+ * interval: the interval between checks for timed out entries. This plus the default represent the maximum time allowed
+ * lazy: leave at false for consistant and fast lookups, set to true for stricter timeout enforcement
+
+ """
+ require Logger
+
+ alias Pleroma.Web.FedSockets.FedRegistry
+ alias Pleroma.Web.FedSockets.FedSocket
+ alias Pleroma.Web.FedSockets.SocketInfo
+
+ @doc """
+ returns a FedSocket for the given origin. Will reuse an existing one or create a new one.
+
+ address is expected to be a fully formed URL such as:
+ "http://www.example.com" or "http://www.example.com:8080"
+
+ It can and usually does include additional path parameters,
+ but these are ignored as the FedSockets are organized by host and port info alone.
+ """
+ def get_or_create_fed_socket(address) do
+ with {:cache, {:error, :missing}} <- {:cache, get_fed_socket(address)},
+ {:connect, {:ok, _pid}} <- {:connect, FedSocket.connect_to_host(address)},
+ {:cache, {:ok, fed_socket}} <- {:cache, get_fed_socket(address)} do
+ Logger.debug("fedsocket created for - #{inspect(address)}")
+ {:ok, fed_socket}
+ else
+ {:cache, {:ok, socket}} ->
+ Logger.debug("fedsocket found in cache - #{inspect(address)}")
+ {:ok, socket}
+
+ {:cache, {:error, :rejected} = e} ->
+ e
+
+ {:connect, {:error, _host}} ->
+ Logger.debug("set host rejected for - #{inspect(address)}")
+ FedRegistry.set_host_rejected(address)
+ {:error, :rejected}
+
+ {_, {:error, :disabled}} ->
+ {:error, :disabled}
+
+ {_, {:error, reason}} ->
+ Logger.warn("get_or_create_fed_socket error - #{inspect(reason)}")
+ {:error, reason}
+ end
+ end
+
+ @doc """
+ returns a FedSocket for the given origin. Will not create a new FedSocket if one does not exist.
+
+ address is expected to be a fully formed URL such as:
+ "http://www.example.com" or "http://www.example.com:8080"
+ """
+ def get_fed_socket(address) do
+ origin = SocketInfo.origin(address)
+
+ with {:config, true} <- {:config, Pleroma.Config.get([:fed_sockets, :enabled], false)},
+ {:ok, socket} <- FedRegistry.get_fed_socket(origin) do
+ {:ok, socket}
+ else
+ {:config, _} ->
+ {:error, :disabled}
+
+ {:error, :rejected} ->
+ Logger.debug("FedSocket previously rejected - #{inspect(origin)}")
+ {:error, :rejected}
+
+ {:error, reason} ->
+ {:error, reason}
+ end
+ end
+
+ @doc """
+ Sends the supplied data via the publish protocol.
+ It will not block waiting for a reply.
+ Returns :ok but this is not an indication of a successful transfer.
+
+ the data is expected to be JSON encoded binary data.
+ """
+ def publish(%SocketInfo{} = fed_socket, json) do
+ FedSocket.publish(fed_socket, json)
+ end
+
+ @doc """
+ Sends the supplied data via the fetch protocol.
+ It will block waiting for a reply or timeout.
+
+ Returns {:ok, object} where object is the requested object (or nil)
+ {:error, :timeout} in the event the message was not responded to
+
+ the id is expected to be the URI of an ActivityPub object.
+ """
+ def fetch(%SocketInfo{} = fed_socket, id) do
+ FedSocket.fetch(fed_socket, id)
+ end
+
+ @doc """
+ Disconnect all and restart FedSockets.
+ This is mainly used in development and testing but could be useful in production.
+ """
+ def reset do
+ FedRegistry
+ |> Process.whereis()
+ |> Process.exit(:testing)
+ end
+
+ def uri_for_origin(origin),
+ do: "ws://#{origin}/api/fedsocket/v1"
+end
diff --git a/lib/pleroma/web/fed_sockets/fed_registry.ex b/lib/pleroma/web/fed_sockets/fed_registry.ex
new file mode 100644
index 000000000..e00ea69c0
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/fed_registry.ex
@@ -0,0 +1,185 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.FedRegistry do
+ @moduledoc """
+ The FedRegistry stores the active FedSockets for quick retrieval.
+
+ The storage and retrieval portion of the FedRegistry is done in process through
+ elixir's `Registry` module for speed and its ability to monitor for terminated processes.
+
+ Dropped connections will be caught by `Registry` and deleted. Since the next
+ message will initiate a new connection there is no reason to try and reconnect at that point.
+
+ Normally outside modules should have no need to call or use the FedRegistry themselves.
+ """
+
+ alias Pleroma.Web.FedSockets.FedSocket
+ alias Pleroma.Web.FedSockets.SocketInfo
+
+ require Logger
+
+ @default_rejection_duration 15 * 60 * 1000
+ @rejections :fed_socket_rejections
+
+ @doc """
+ Retrieves a FedSocket from the Registry given it's origin.
+
+ The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
+
+ Will return:
+ * {:ok, fed_socket} for working FedSockets
+ * {:error, :rejected} for origins that have been tried and refused within the rejection duration interval
+ * {:error, some_reason} usually :missing for unknown origins
+ """
+ def get_fed_socket(origin) do
+ case get_registry_data(origin) do
+ {:error, reason} ->
+ {:error, reason}
+
+ {:ok, %{state: :connected} = socket_info} ->
+ {:ok, socket_info}
+ end
+ end
+
+ @doc """
+ Adds a connected FedSocket to the Registry.
+
+ Always returns {:ok, fed_socket}
+ """
+ def add_fed_socket(origin, pid \\ nil) do
+ origin
+ |> SocketInfo.build(pid)
+ |> SocketInfo.connect()
+ |> add_socket_info
+ end
+
+ defp add_socket_info(%{origin: origin, state: :connected} = socket_info) do
+ case Registry.register(FedSockets.Registry, origin, socket_info) do
+ {:ok, _owner} ->
+ clear_prior_rejection(origin)
+ Logger.debug("fedsocket added: #{inspect(origin)}")
+
+ {:ok, socket_info}
+
+ {:error, {:already_registered, _pid}} ->
+ FedSocket.close(socket_info)
+ existing_socket_info = Registry.lookup(FedSockets.Registry, origin)
+
+ {:ok, existing_socket_info}
+
+ _ ->
+ {:error, :error_adding_socket}
+ end
+ end
+
+ @doc """
+ Mark this origin as having rejected a connection attempt.
+ This will keep it from getting additional connection attempts
+ for a period of time specified in the config.
+
+ Always returns {:ok, new_reg_data}
+ """
+ def set_host_rejected(uri) do
+ new_reg_data =
+ uri
+ |> SocketInfo.origin()
+ |> get_or_create_registry_data()
+ |> set_to_rejected()
+ |> save_registry_data()
+
+ {:ok, new_reg_data}
+ end
+
+ @doc """
+ Retrieves the FedRegistryData from the Registry given it's origin.
+
+ The origin is expected to be a string identifying the endpoint "example.com" or "example2.com:8080"
+
+ Will return:
+ * {:ok, fed_registry_data} for known origins
+ * {:error, :missing} for uniknown origins
+ * {:error, :cache_error} indicating some low level runtime issues
+ """
+ def get_registry_data(origin) do
+ case Registry.lookup(FedSockets.Registry, origin) do
+ [] ->
+ if is_rejected?(origin) do
+ Logger.debug("previously rejected fedsocket requested")
+ {:error, :rejected}
+ else
+ {:error, :missing}
+ end
+
+ [{_pid, %{state: :connected} = socket_info}] ->
+ {:ok, socket_info}
+
+ _ ->
+ {:error, :cache_error}
+ end
+ end
+
+ @doc """
+ Retrieves a map of all sockets from the Registry. The keys are the origins and the values are the corresponding SocketInfo
+ """
+ def list_all do
+ (list_all_connected() ++ list_all_rejected())
+ |> Enum.into(%{})
+ end
+
+ defp list_all_connected do
+ FedSockets.Registry
+ |> Registry.select([{{:"$1", :_, :"$3"}, [], [{{:"$1", :"$3"}}]}])
+ end
+
+ defp list_all_rejected do
+ {:ok, keys} = Cachex.keys(@rejections)
+
+ {:ok, registry_data} =
+ Cachex.execute(@rejections, fn worker ->
+ Enum.map(keys, fn k -> {k, Cachex.get!(worker, k)} end)
+ end)
+
+ registry_data
+ end
+
+ defp clear_prior_rejection(origin),
+ do: Cachex.del(@rejections, origin)
+
+ defp is_rejected?(origin) do
+ case Cachex.get(@rejections, origin) do
+ {:ok, nil} ->
+ false
+
+ {:ok, _} ->
+ true
+ end
+ end
+
+ defp get_or_create_registry_data(origin) do
+ case get_registry_data(origin) do
+ {:error, :missing} ->
+ %SocketInfo{origin: origin}
+
+ {:ok, socket_info} ->
+ socket_info
+ end
+ end
+
+ defp save_registry_data(%SocketInfo{origin: origin, state: :connected} = socket_info) do
+ {:ok, true} = Registry.update_value(FedSockets.Registry, origin, fn _ -> socket_info end)
+ socket_info
+ end
+
+ defp save_registry_data(%SocketInfo{origin: origin, state: :rejected} = socket_info) do
+ rejection_expiration =
+ Pleroma.Config.get([:fed_sockets, :rejection_duration], @default_rejection_duration)
+
+ {:ok, true} = Cachex.put(@rejections, origin, socket_info, ttl: rejection_expiration)
+ socket_info
+ end
+
+ defp set_to_rejected(%SocketInfo{} = socket_info),
+ do: %SocketInfo{socket_info | state: :rejected}
+end
diff --git a/lib/pleroma/web/fed_sockets/fed_socket.ex b/lib/pleroma/web/fed_sockets/fed_socket.ex
new file mode 100644
index 000000000..98d64e65a
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/fed_socket.ex
@@ -0,0 +1,137 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.FedSocket do
+ @moduledoc """
+ The FedSocket module abstracts the actions to be taken taken on connections regardless of
+ whether the connection started as inbound or outbound.
+
+
+ Normally outside modules will have no need to call the FedSocket module directly.
+ """
+
+ alias Pleroma.Object
+ alias Pleroma.Object.Containment
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ObjectView
+ alias Pleroma.Web.ActivityPub.UserView
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.FedSockets.FetchRegistry
+ alias Pleroma.Web.FedSockets.IngesterWorker
+ alias Pleroma.Web.FedSockets.OutgoingHandler
+ alias Pleroma.Web.FedSockets.SocketInfo
+
+ require Logger
+
+ @shake "61dd18f7-f1e6-49a4-939a-a749fcdc1103"
+
+ def connect_to_host(uri) do
+ case OutgoingHandler.start_link(uri) do
+ {:ok, pid} ->
+ {:ok, pid}
+
+ error ->
+ {:error, error}
+ end
+ end
+
+ def close(%SocketInfo{pid: socket_pid}),
+ do: Process.send(socket_pid, :close, [])
+
+ def publish(%SocketInfo{pid: socket_pid}, json) do
+ %{action: :publish, data: json}
+ |> Jason.encode!()
+ |> send_packet(socket_pid)
+ end
+
+ def fetch(%SocketInfo{pid: socket_pid}, id) do
+ fetch_uuid = FetchRegistry.register_fetch(id)
+
+ %{action: :fetch, data: id, uuid: fetch_uuid}
+ |> Jason.encode!()
+ |> send_packet(socket_pid)
+
+ wait_for_fetch_to_return(fetch_uuid, 0)
+ end
+
+ def receive_package(%SocketInfo{} = fed_socket, json) do
+ json
+ |> Jason.decode!()
+ |> process_package(fed_socket)
+ end
+
+ defp wait_for_fetch_to_return(uuid, cntr) do
+ case FetchRegistry.check_fetch(uuid) do
+ {:error, :waiting} ->
+ Process.sleep(:math.pow(cntr, 3) |> Kernel.trunc())
+ wait_for_fetch_to_return(uuid, cntr + 1)
+
+ {:error, :missing} ->
+ Logger.error("FedSocket fetch timed out - #{inspect(uuid)}")
+ {:error, :timeout}
+
+ {:ok, _fr} ->
+ FetchRegistry.pop_fetch(uuid)
+ end
+ end
+
+ defp process_package(%{"action" => "publish", "data" => data}, %{origin: origin} = _fed_socket) do
+ if Containment.contain_origin(origin, data) do
+ IngesterWorker.enqueue("ingest", %{"object" => data})
+ end
+
+ {:reply, %{"action" => "publish_reply", "status" => "processed"}}
+ end
+
+ defp process_package(%{"action" => "fetch_reply", "uuid" => uuid, "data" => data}, _fed_socket) do
+ FetchRegistry.register_fetch_received(uuid, data)
+ {:noreply, nil}
+ end
+
+ defp process_package(%{"action" => "fetch", "uuid" => uuid, "data" => ap_id}, _fed_socket) do
+ {:ok, data} = render_fetched_data(ap_id, uuid)
+ {:reply, data}
+ end
+
+ defp process_package(%{"action" => "publish_reply"}, _fed_socket) do
+ {:noreply, nil}
+ end
+
+ defp process_package(other, _fed_socket) do
+ Logger.warn("unknown json packages received #{inspect(other)}")
+ {:noreply, nil}
+ end
+
+ defp render_fetched_data(ap_id, uuid) do
+ {:ok,
+ %{
+ "action" => "fetch_reply",
+ "status" => "processed",
+ "uuid" => uuid,
+ "data" => represent_item(ap_id)
+ }}
+ end
+
+ defp represent_item(ap_id) do
+ case User.get_by_ap_id(ap_id) do
+ nil ->
+ object = Object.get_cached_by_ap_id(ap_id)
+
+ if Visibility.is_public?(object) do
+ Phoenix.View.render_to_string(ObjectView, "object.json", object: object)
+ else
+ nil
+ end
+
+ user ->
+ Phoenix.View.render_to_string(UserView, "user.json", user: user)
+ end
+ end
+
+ defp send_packet(data, socket_pid) do
+ Process.send(socket_pid, {:send, data}, [])
+ end
+
+ def shake, do: @shake
+end
diff --git a/lib/pleroma/web/fed_sockets/fetch_registry.ex b/lib/pleroma/web/fed_sockets/fetch_registry.ex
new file mode 100644
index 000000000..7897f0fc6
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/fetch_registry.ex
@@ -0,0 +1,151 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.FetchRegistry do
+ @moduledoc """
+ The FetchRegistry acts as a broker for fetch requests and return values.
+ This allows calling processes to block while waiting for a reply.
+ It doesn't impose it's own process instead using `Cachex` to handle fetches in process, allowing
+ multi threaded processes to avoid bottlenecking.
+
+ Normally outside modules will have no need to call or use the FetchRegistry themselves.
+
+ The `Cachex` parameters can be controlled from the config. Since exact timeout intervals
+ aren't necessary the following settings are used by default:
+
+ config :pleroma, :fed_sockets,
+ fed_socket_fetches: [
+ default: 12_000,
+ interval: 3_000,
+ lazy: false
+ ]
+
+ """
+
+ defmodule FetchRegistryData do
+ defstruct uuid: nil,
+ sent_json: nil,
+ received_json: nil,
+ sent_at: nil,
+ received_at: nil
+ end
+
+ alias Ecto.UUID
+
+ require Logger
+
+ @fetches :fed_socket_fetches
+
+ @doc """
+ Registers a json request wth the FetchRegistry and returns the identifying UUID.
+ """
+ def register_fetch(json) do
+ %FetchRegistryData{uuid: uuid} =
+ json
+ |> new_registry_data
+ |> save_registry_data
+
+ uuid
+ end
+
+ @doc """
+ Reports on the status of a Fetch given the identifying UUID.
+
+ Will return
+ * {:ok, fetched_object} if a fetch has completed
+ * {:error, :waiting} if a fetch is still pending
+ * {:error, other_error} usually :missing to indicate a fetch that has timed out
+ """
+ def check_fetch(uuid) do
+ case get_registry_data(uuid) do
+ {:ok, %FetchRegistryData{received_at: nil}} ->
+ {:error, :waiting}
+
+ {:ok, %FetchRegistryData{} = reg_data} ->
+ {:ok, reg_data}
+
+ e ->
+ e
+ end
+ end
+
+ @doc """
+ Retrieves the response to a fetch given the identifying UUID.
+ The completed fetch will be deleted from the FetchRegistry
+
+ Will return
+ * {:ok, fetched_object} if a fetch has completed
+ * {:error, :waiting} if a fetch is still pending
+ * {:error, other_error} usually :missing to indicate a fetch that has timed out
+ """
+ def pop_fetch(uuid) do
+ case check_fetch(uuid) do
+ {:ok, %FetchRegistryData{received_json: received_json}} ->
+ delete_registry_data(uuid)
+ {:ok, received_json}
+
+ e ->
+ e
+ end
+ end
+
+ @doc """
+ This is called to register a fetch has returned.
+ It expects the result data along with the UUID that was sent in the request
+
+ Will return the fetched object or :error
+ """
+ def register_fetch_received(uuid, data) do
+ case get_registry_data(uuid) do
+ {:ok, %FetchRegistryData{received_at: nil} = reg_data} ->
+ reg_data
+ |> set_fetch_received(data)
+ |> save_registry_data()
+
+ {:ok, %FetchRegistryData{} = reg_data} ->
+ Logger.warn("tried to add fetched data twice - #{uuid}")
+ reg_data
+
+ {:error, _} ->
+ Logger.warn("Error adding fetch to registry - #{uuid}")
+ :error
+ end
+ end
+
+ defp new_registry_data(json) do
+ %FetchRegistryData{
+ uuid: UUID.generate(),
+ sent_json: json,
+ sent_at: :erlang.monotonic_time(:millisecond)
+ }
+ end
+
+ defp get_registry_data(origin) do
+ case Cachex.get(@fetches, origin) do
+ {:ok, nil} ->
+ {:error, :missing}
+
+ {:ok, reg_data} ->
+ {:ok, reg_data}
+
+ _ ->
+ {:error, :cache_error}
+ end
+ end
+
+ defp set_fetch_received(%FetchRegistryData{} = reg_data, data),
+ do: %FetchRegistryData{
+ reg_data
+ | received_at: :erlang.monotonic_time(:millisecond),
+ received_json: data
+ }
+
+ defp save_registry_data(%FetchRegistryData{uuid: uuid} = reg_data) do
+ {:ok, true} = Cachex.put(@fetches, uuid, reg_data)
+ reg_data
+ end
+
+ defp delete_registry_data(origin),
+ do: {:ok, true} = Cachex.del(@fetches, origin)
+end
diff --git a/lib/pleroma/web/fed_sockets/incoming_handler.ex b/lib/pleroma/web/fed_sockets/incoming_handler.ex
new file mode 100644
index 000000000..49d0d9d84
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/incoming_handler.ex
@@ -0,0 +1,88 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.IncomingHandler do
+ require Logger
+
+ alias Pleroma.Web.FedSockets.FedRegistry
+ alias Pleroma.Web.FedSockets.FedSocket
+ alias Pleroma.Web.FedSockets.SocketInfo
+
+ import HTTPSignatures, only: [validate_conn: 1, split_signature: 1]
+
+ @behaviour :cowboy_websocket
+
+ def init(req, state) do
+ shake = FedSocket.shake()
+
+ with true <- Pleroma.Config.get([:fed_sockets, :enabled]),
+ sec_protocol <- :cowboy_req.header("sec-websocket-protocol", req, nil),
+ headers = %{"(request-target)" => ^shake} <- :cowboy_req.headers(req),
+ true <- validate_conn(%{req_headers: headers}),
+ %{"keyId" => origin} <- split_signature(headers["signature"]) do
+ req =
+ if is_nil(sec_protocol) do
+ req
+ else
+ :cowboy_req.set_resp_header("sec-websocket-protocol", sec_protocol, req)
+ end
+
+ {:cowboy_websocket, req, %{origin: origin}, %{}}
+ else
+ _ ->
+ {:ok, req, state}
+ end
+ end
+
+ def websocket_init(%{origin: origin}) do
+ case FedRegistry.add_fed_socket(origin) do
+ {:ok, socket_info} ->
+ {:ok, socket_info}
+
+ e ->
+ Logger.error("FedSocket websocket_init failed - #{inspect(e)}")
+ {:error, inspect(e)}
+ end
+ end
+
+ # Use the ping to check if the connection should be expired
+ def websocket_handle(:ping, socket_info) do
+ if SocketInfo.expired?(socket_info) do
+ {:stop, socket_info}
+ else
+ {:ok, socket_info, :hibernate}
+ end
+ end
+
+ def websocket_handle({:text, data}, socket_info) do
+ socket_info = SocketInfo.touch(socket_info)
+
+ case FedSocket.receive_package(socket_info, data) do
+ {:noreply, _} ->
+ {:ok, socket_info}
+
+ {:reply, reply} ->
+ {:reply, {:text, Jason.encode!(reply)}, socket_info}
+
+ {:error, reason} ->
+ Logger.error("incoming error - receive_package: #{inspect(reason)}")
+ {:ok, socket_info}
+ end
+ end
+
+ def websocket_info({:send, message}, socket_info) do
+ socket_info = SocketInfo.touch(socket_info)
+
+ {:reply, {:text, message}, socket_info}
+ end
+
+ def websocket_info(:close, state) do
+ {:stop, state}
+ end
+
+ def websocket_info(message, state) do
+ Logger.debug("#{__MODULE__} unknown message #{inspect(message)}")
+ {:ok, state}
+ end
+end
diff --git a/lib/pleroma/web/fed_sockets/ingester_worker.ex b/lib/pleroma/web/fed_sockets/ingester_worker.ex
new file mode 100644
index 000000000..325f2a4ab
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/ingester_worker.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.IngesterWorker do
+ use Pleroma.Workers.WorkerHelper, queue: "ingestion_queue"
+ require Logger
+
+ alias Pleroma.Web.Federator
+
+ @impl Oban.Worker
+ def perform(%Job{args: %{"op" => "ingest", "object" => ingestee}}) do
+ try do
+ ingestee
+ |> Jason.decode!()
+ |> do_ingestion()
+ rescue
+ e ->
+ Logger.error("IngesterWorker error - #{inspect(e)}")
+ e
+ end
+ end
+
+ defp do_ingestion(params) do
+ case Federator.incoming_ap_doc(params) do
+ {:error, reason} ->
+ {:error, reason}
+
+ {:ok, object} ->
+ {:ok, object}
+ end
+ end
+end
diff --git a/lib/pleroma/web/fed_sockets/outgoing_handler.ex b/lib/pleroma/web/fed_sockets/outgoing_handler.ex
new file mode 100644
index 000000000..e235a7c43
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/outgoing_handler.ex
@@ -0,0 +1,151 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.OutgoingHandler do
+ use GenServer
+
+ require Logger
+
+ alias Pleroma.Application
+ alias Pleroma.Web.ActivityPub.InternalFetchActor
+ alias Pleroma.Web.FedSockets
+ alias Pleroma.Web.FedSockets.FedRegistry
+ alias Pleroma.Web.FedSockets.FedSocket
+ alias Pleroma.Web.FedSockets.SocketInfo
+
+ def start_link(uri) do
+ GenServer.start_link(__MODULE__, %{uri: uri})
+ end
+
+ def init(%{uri: uri}) do
+ case initiate_connection(uri) do
+ {:ok, ws_origin, conn_pid} ->
+ FedRegistry.add_fed_socket(ws_origin, conn_pid)
+
+ {:error, reason} ->
+ Logger.debug("Outgoing connection failed - #{inspect(reason)}")
+ :ignore
+ end
+ end
+
+ def handle_info({:gun_ws, conn_pid, _ref, {:text, data}}, socket_info) do
+ socket_info = SocketInfo.touch(socket_info)
+
+ case FedSocket.receive_package(socket_info, data) do
+ {:noreply, _} ->
+ {:noreply, socket_info}
+
+ {:reply, reply} ->
+ :gun.ws_send(conn_pid, {:text, Jason.encode!(reply)})
+ {:noreply, socket_info}
+
+ {:error, reason} ->
+ Logger.error("incoming error - receive_package: #{inspect(reason)}")
+ {:noreply, socket_info}
+ end
+ end
+
+ def handle_info(:close, state) do
+ Logger.debug("Sending close frame !!!!!!!")
+ {:close, state}
+ end
+
+ def handle_info({:gun_down, _pid, _prot, :closed, _}, state) do
+ {:stop, :normal, state}
+ end
+
+ def handle_info({:send, data}, %{conn_pid: conn_pid} = socket_info) do
+ socket_info = SocketInfo.touch(socket_info)
+ :gun.ws_send(conn_pid, {:text, data})
+ {:noreply, socket_info}
+ end
+
+ def handle_info({:gun_ws, _, _, :pong}, state) do
+ {:noreply, state, :hibernate}
+ end
+
+ def handle_info(msg, state) do
+ Logger.debug("#{__MODULE__} unhandled event #{inspect(msg)}")
+ {:noreply, state}
+ end
+
+ def terminate(reason, state) do
+ Logger.debug(
+ "#{__MODULE__} terminating outgoing connection for #{inspect(state)} for #{inspect(reason)}"
+ )
+
+ {:ok, state}
+ end
+
+ def initiate_connection(uri) do
+ ws_uri =
+ uri
+ |> SocketInfo.origin()
+ |> FedSockets.uri_for_origin()
+
+ %{host: host, port: port, path: path} = URI.parse(ws_uri)
+
+ with {:ok, conn_pid} <- :gun.open(to_charlist(host), port, %{protocols: [:http]}),
+ {:ok, _} <- :gun.await_up(conn_pid),
+ reference <-
+ :gun.get(conn_pid, to_charlist(path), [
+ {'user-agent', to_charlist(Application.user_agent())}
+ ]),
+ {:response, :fin, 204, _} <- :gun.await(conn_pid, reference),
+ headers <- build_headers(uri),
+ ref <- :gun.ws_upgrade(conn_pid, to_charlist(path), headers, %{silence_pings: false}) do
+ receive do
+ {:gun_upgrade, ^conn_pid, ^ref, [<<"websocket">>], _} ->
+ {:ok, ws_uri, conn_pid}
+ after
+ 15_000 ->
+ Logger.debug("Fedsocket timeout connecting to #{inspect(uri)}")
+ {:error, :timeout}
+ end
+ else
+ {:response, :nofin, 404, _} ->
+ {:error, :fedsockets_not_supported}
+
+ e ->
+ Logger.debug("Fedsocket error connecting to #{inspect(uri)}")
+ {:error, e}
+ end
+ end
+
+ defp build_headers(uri) do
+ host_for_sig = uri |> URI.parse() |> host_signature()
+
+ shake = FedSocket.shake()
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, shake) |> Base.encode64())
+ date = Pleroma.Signature.signed_date()
+ shake_size = byte_size(shake)
+
+ signature_opts = %{
+ "(request-target)": shake,
+ "content-length": to_charlist("#{shake_size}"),
+ date: date,
+ digest: digest,
+ host: host_for_sig
+ }
+
+ signature = Pleroma.Signature.sign(InternalFetchActor.get_actor(), signature_opts)
+
+ [
+ {'signature', to_charlist(signature)},
+ {'date', date},
+ {'digest', to_charlist(digest)},
+ {'content-length', to_charlist("#{shake_size}")},
+ {to_charlist("(request-target)"), to_charlist(shake)},
+ {'user-agent', to_charlist(Application.user_agent())}
+ ]
+ end
+
+ defp host_signature(%{host: host, scheme: scheme, port: port}) do
+ if port == URI.default_port(scheme) do
+ host
+ else
+ "#{host}:#{port}"
+ end
+ end
+end
diff --git a/lib/pleroma/web/fed_sockets/socket_info.ex b/lib/pleroma/web/fed_sockets/socket_info.ex
new file mode 100644
index 000000000..d6fdffe1a
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/socket_info.ex
@@ -0,0 +1,52 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.SocketInfo do
+ defstruct origin: nil,
+ pid: nil,
+ conn_pid: nil,
+ state: :default,
+ connected_until: nil
+
+ alias Pleroma.Web.FedSockets.SocketInfo
+ @default_connection_duration 15 * 60 * 1000
+
+ def build(uri, conn_pid \\ nil) do
+ uri
+ |> build_origin()
+ |> build_pids(conn_pid)
+ |> touch()
+ end
+
+ def touch(%SocketInfo{} = socket_info),
+ do: %{socket_info | connected_until: new_ttl()}
+
+ def connect(%SocketInfo{} = socket_info),
+ do: %{socket_info | state: :connected}
+
+ def expired?(%{connected_until: connected_until}),
+ do: connected_until < :erlang.monotonic_time(:millisecond)
+
+ def origin(uri),
+ do: build_origin(uri).origin
+
+ defp build_pids(socket_info, conn_pid),
+ do: struct(socket_info, pid: self(), conn_pid: conn_pid)
+
+ defp build_origin(uri) when is_binary(uri),
+ do: uri |> URI.parse() |> build_origin
+
+ defp build_origin(%{host: host, port: nil, scheme: scheme}),
+ do: build_origin(%{host: host, port: URI.default_port(scheme)})
+
+ defp build_origin(%{host: host, port: port}),
+ do: %SocketInfo{origin: "#{host}:#{port}"}
+
+ defp new_ttl do
+ connection_duration =
+ Pleroma.Config.get([:fed_sockets, :connection_duration], @default_connection_duration)
+
+ :erlang.monotonic_time(:millisecond) + connection_duration
+ end
+end
diff --git a/lib/pleroma/web/fed_sockets/supervisor.ex b/lib/pleroma/web/fed_sockets/supervisor.ex
new file mode 100644
index 000000000..a5f4bebfb
--- /dev/null
+++ b/lib/pleroma/web/fed_sockets/supervisor.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.FedSockets.Supervisor do
+ use Supervisor
+ import Cachex.Spec
+
+ def start_link(opts) do
+ Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
+ end
+
+ def init(args) do
+ children = [
+ build_cache(:fed_socket_fetches, args),
+ build_cache(:fed_socket_rejections, args),
+ {Registry, keys: :unique, name: FedSockets.Registry, meta: [rejected: %{}]}
+ ]
+
+ opts = [strategy: :one_for_all, name: Pleroma.Web.Streamer.Supervisor]
+ Supervisor.init(children, opts)
+ end
+
+ defp build_cache(name, args) do
+ opts = get_opts(name, args)
+
+ %{
+ id: String.to_atom("#{name}_cache"),
+ start: {Cachex, :start_link, [name, opts]},
+ type: :worker
+ }
+ end
+
+ defp get_opts(cache_name, args)
+ when cache_name in [:fed_socket_fetches, :fed_socket_rejections] do
+ default = get_opts_or_config(args, cache_name, :default, 15_000)
+ interval = get_opts_or_config(args, cache_name, :interval, 3_000)
+ lazy = get_opts_or_config(args, cache_name, :lazy, false)
+
+ [expiration: expiration(default: default, interval: interval, lazy: lazy)]
+ end
+
+ defp get_opts(name, args) do
+ Keyword.get(args, name, [])
+ end
+
+ defp get_opts_or_config(args, name, key, default) do
+ args
+ |> Keyword.get(name, [])
+ |> Keyword.get(key)
+ |> case do
+ nil ->
+ Pleroma.Config.get([:fed_sockets, name, key], default)
+
+ value ->
+ value
+ end
+ end
+end
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator.ex
index f5803578d..130654145 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -66,14 +66,17 @@ defmodule Pleroma.Web.Federator do
def perform(:incoming_ap_doc, params) do
Logger.debug("Handling incoming AP activity")
- params = Utils.normalize_params(params)
+ actor =
+ params
+ |> Map.get("actor")
+ |> Utils.get_ap_id()
# NOTE: we use the actor ID to do the containment, this is fine because an
# actor shouldn't be acting on objects outside their own AP server.
- with {:ok, _user} <- ap_enabled_actor(params["actor"]),
+ with {_, {:ok, _user}} <- {:actor, ap_enabled_actor(actor)},
nil <- Activity.normalize(params["id"]),
{_, :ok} <-
- {:correct_origin?, Containment.contain_origin_from_id(params["actor"], params)},
+ {:correct_origin?, Containment.contain_origin_from_id(actor, params)},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, activity}
else
@@ -85,10 +88,13 @@ defmodule Pleroma.Web.Federator do
Logger.debug("Already had #{params["id"]}")
{:error, :already_present}
+ {:actor, e} ->
+ Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
+ {:error, e}
+
e ->
# Just drop those for now
- Logger.debug("Unhandled activity")
- Logger.debug(Jason.encode!(params, pretty: true))
+ Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
{:error, e}
end
end
diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex
index 93a8294b7..218cdbdf3 100644
--- a/lib/pleroma/web/feed/tag_controller.ex
+++ b/lib/pleroma/web/feed/tag_controller.ex
@@ -10,14 +10,14 @@ defmodule Pleroma.Web.Feed.TagController do
alias Pleroma.Web.Feed.FeedView
def feed(conn, params) do
- unless Pleroma.Config.restrict_unauthenticated_access?(:activities, :local) do
+ if Config.get!([:instance, :public]) do
render_feed(conn, params)
else
render_error(conn, :not_found, "Not found")
end
end
- def render_feed(conn, %{"tag" => raw_tag} = params) do
+ defp render_feed(conn, %{"tag" => raw_tag} = params) do
{format, tag} = parse_tag(raw_tag)
activities =
@@ -36,12 +36,13 @@ defmodule Pleroma.Web.Feed.TagController do
end
@spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()}
- defp parse_tag(raw_tag) when is_binary(raw_tag) do
- case Enum.reverse(String.split(raw_tag, ".")) do
- [format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")}
- _ -> {"rss", raw_tag}
+ defp parse_tag(raw_tag) do
+ case is_binary(raw_tag) && Enum.reverse(String.split(raw_tag, ".")) do
+ [format | tag] when format in ["rss", "atom"] ->
+ {format, Enum.join(tag, ".")}
+
+ _ ->
+ {"atom", raw_tag}
end
end
-
- defp parse_tag(raw_tag), do: {"rss", raw_tag}
end
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
index 71eb1ea7e..a5013d2c0 100644
--- a/lib/pleroma/web/feed/user_controller.ex
+++ b/lib/pleroma/web/feed/user_controller.ex
@@ -5,30 +5,25 @@
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
- alias Fallback.RedirectController
+ alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.Feed.FeedView
- plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
+ plug(Pleroma.Web.Plugs.SetFormatPlug when action in [:feed_redirect])
action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
- RedirectController.redirector_with_meta(conn, %{user: user})
+ Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
end
end
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
- with %{halted: false} = conn <-
- Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn,
- unless_func: &Pleroma.Web.FederatingPlug.federating?/1
- ) do
- ActivityPubController.call(conn, :user)
- end
+ ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do
@@ -37,25 +32,18 @@ defmodule Pleroma.Web.Feed.UserController do
end
end
- def feed(conn, params) do
- unless Pleroma.Config.restrict_unauthenticated_access?(:profiles, :local) do
- render_feed(conn, params)
- else
- errors(conn, {:error, :not_found})
- end
- end
-
- def render_feed(conn, %{"nickname" => nickname} = params) do
+ def feed(conn, %{"nickname" => nickname} = params) do
format = get_format(conn)
format =
- if format in ["rss", "atom"] do
+ if format in ["atom", "rss"] do
format
else
"atom"
end
- with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
+ with {_, %User{local: true} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
activities =
%{
type: ["Create"],
@@ -70,7 +58,7 @@ defmodule Pleroma.Web.Feed.UserController do
|> render("user.#{format}",
user: user,
activities: activities,
- feed_config: Pleroma.Config.get([:feed])
+ feed_config: Config.get([:feed])
)
end
end
@@ -82,6 +70,8 @@ defmodule Pleroma.Web.Feed.UserController do
def errors(conn, {:fetch_user, %User{local: false}}), do: errors(conn, {:error, :not_found})
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+ def errors(conn, {:visibility, _}), do: errors(conn, {:error, :not_found})
+
def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end
diff --git a/lib/pleroma/web/instance_document.ex b/lib/pleroma/web/instance_document.ex
new file mode 100644
index 000000000..df5caebf0
--- /dev/null
+++ b/lib/pleroma/web/instance_document.ex
@@ -0,0 +1,62 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.InstanceDocument do
+ alias Pleroma.Config
+ alias Pleroma.Web.Endpoint
+
+ @instance_documents %{
+ "terms-of-service" => "/static/terms-of-service.html",
+ "instance-panel" => "/instance/panel.html"
+ }
+
+ @spec get(String.t()) :: {:ok, String.t()} | {:error, atom()}
+ def get(document_name) do
+ case Map.fetch(@instance_documents, document_name) do
+ {:ok, path} -> {:ok, path}
+ _ -> {:error, :not_found}
+ end
+ end
+
+ @spec put(String.t(), String.t()) :: {:ok, String.t()} | {:error, atom()}
+ def put(document_name, origin_path) do
+ with {_, {:ok, destination_path}} <-
+ {:instance_document, Map.fetch(@instance_documents, document_name)},
+ :ok <- put_file(origin_path, destination_path) do
+ {:ok, Path.join(Endpoint.url(), destination_path)}
+ else
+ {:instance_document, :error} -> {:error, :not_found}
+ error -> error
+ end
+ end
+
+ @spec delete(String.t()) :: :ok | {:error, atom()}
+ def delete(document_name) do
+ with {_, {:ok, path}} <- {:instance_document, Map.fetch(@instance_documents, document_name)},
+ instance_static_dir_path <- instance_static_dir(path),
+ :ok <- File.rm(instance_static_dir_path) do
+ :ok
+ else
+ {:instance_document, :error} -> {:error, :not_found}
+ {:error, :enoent} -> {:error, :not_found}
+ error -> error
+ end
+ end
+
+ defp put_file(origin_path, destination_path) do
+ with destination <- instance_static_dir(destination_path),
+ {_, :ok} <- {:mkdir_p, File.mkdir_p(Path.dirname(destination))},
+ {_, {:ok, _}} <- {:copy, File.copy(origin_path, destination)} do
+ :ok
+ else
+ {error, _} -> {:error, error}
+ end
+ end
+
+ defp instance_static_dir(filename) do
+ [:instance, :static_dir]
+ |> Config.get!()
+ |> Path.join(filename)
+ end
+end
diff --git a/lib/pleroma/web/mailer/subscription_controller.ex b/lib/pleroma/web/mailer/subscription_controller.ex
index 478a83518..ace44afd1 100644
--- a/lib/pleroma/web/mailer/subscription_controller.ex
+++ b/lib/pleroma/web/mailer/subscription_controller.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Mailer.SubscriptionController do
use Pleroma.Web, :controller
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index 43ec70021..08f92d55f 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 95d8452df..97858a93c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -15,9 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
]
alias Pleroma.Maps
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
@@ -29,6 +26,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.OAuthView
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@@ -177,7 +177,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
user_params =
[
:no_rich_text,
- :locked,
:hide_followers_count,
:hide_follows_count,
:hide_followers,
@@ -210,6 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
if bot, do: {:ok, "Service"}, else: {:ok, "Person"}
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
+ |> Maps.put_if_present(:is_locked, params[:locked])
# What happens here:
#
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
index a516b6c20..143dcf80c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -5,12 +5,12 @@
defmodule Pleroma.Web.MastodonAPI.AppController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
index 9f09550e1..9cc3984d0 100644
--- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -5,6 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.AuthController do
use Pleroma.Web, :controller
+ import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
alias Pleroma.User
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
@@ -13,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
- plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
@local_mastodon_name "Mastodon-Local"
@@ -22,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
redirect(conn, to: local_mastodon_root_path(conn))
end
- @doc "Local Mastodon FE login init action"
+ # Local Mastodon FE login init action
def login(conn, %{"code" => auth_token}) do
with {:ok, app} <- get_or_make_app(),
{:ok, auth} <- Authorization.get_by_token(app, auth_token),
@@ -33,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
end
end
- @doc "Local Mastodon FE callback action"
+ # Local Mastodon FE callback action
def login(conn, _) do
with {:ok, app} <- get_or_make_app() do
path =
@@ -61,9 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
TwitterAPI.password_reset(nickname_or_email)
- conn
- |> put_status(:no_content)
- |> json("")
+ json_response(conn, :no_content, "")
end
defp local_mastodon_root_path(conn) do
diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
index f35ec3596..61347d8db 100644
--- a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
index c5f47c5df..872cb1f4d 100644
--- a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
plug(
:skip_plug,
- [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
when action == :index
)
diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
index 9c2d093cd..503bd7d5f 100644
--- a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
index abbf0ce02..c71a34b15 100644
--- a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
use Pleroma.Web, :controller
alias Pleroma.Filter
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
index 748b6b475..f8cd7fa9f 100644
--- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
index d8859731d..07a32491a 100644
--- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do
plug(
:skip_plug,
- [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:show, :peers]
)
diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
index acdc76fd2..f6b51bf02 100644
--- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastodonAPI.ListController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:index, :show, :list_accounts]
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
# DELETE /api/v1/lists/:id/accounts
def remove_from_list(
- %{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn,
+ %{assigns: %{list: list}, params: %{account_ids: account_ids}} = conn,
_
) do
Enum.each(account_ids, fn account_id ->
@@ -86,6 +86,10 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
json(conn, %{})
end
+ def remove_from_list(%{body_params: params} = conn, _) do
+ remove_from_list(%{conn | params: params}, %{})
+ end
+
defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do
case Pleroma.List.get(id, user) do
%Pleroma.List{} = list -> assign(conn, :list, list)
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
index 85310edfa..0628b2b49 100644
--- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -4,7 +4,7 @@
defmodule Pleroma.Web.MastodonAPI.MarkerController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index e7767de4e..9cf682c7b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
plug(
:skip_plug,
- [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:empty_array, :empty_object]
)
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
index 513de279f..161193134 100644
--- a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -6,11 +6,12 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
use Pleroma.Web, :controller
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:create, :create2])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index e25cef30b..c3c8606f2 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Notification
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.MastodonAPI.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
index db46ffcfc..3dcd1c44f 100644
--- a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
@@ -9,9 +9,9 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
alias Pleroma.Activity
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
index 405167108..156544f40 100644
--- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
@@ -3,14 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportController do
- alias Pleroma.Plugs.OAuthScopesPlug
-
use Pleroma.Web, :controller
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
index 1719c67ea..322a46497 100644
--- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ScheduledActivity
alias Pleroma.Web.MastodonAPI.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
index 5a983db39..0043c3a56 100644
--- a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
alias Pleroma.Activity
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
require Logger
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index ecfa38489..6848adace 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -13,8 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
@@ -23,9 +21,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show])
+
+ plug(
+ :skip_plug,
+ Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]
+ )
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
@@ -123,9 +127,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
@doc """
POST /api/v1/statuses
-
- Creates a scheduled status when `scheduled_at` param is present and it's far enough
"""
+ # Creates a scheduled status when `scheduled_at` param is present and it's far enough
def create(
%{
assigns: %{user: user},
@@ -156,11 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
end
- @doc """
- POST /api/v1/statuses
-
- Creates a regular status
- """
+ # Creates a regular status
def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn, _) do
params = Map.put(params, :in_reply_to_status_id, params[:in_reply_to_id])
diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
index 34eac97c5..20138908c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:restrict_push_enabled)
- plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
index f91df9ab7..5765271cf 100644
--- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
index 5272790d3..7a5c80e01 100644
--- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -10,11 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.Config
alias Pleroma.Pagination
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag])
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 864c0417f..d54cae732 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -181,8 +181,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
user = User.sanitize_html(user, User.html_filter_policy(opts[:for]))
display_name = user.name || user.nickname
- image = User.avatar_url(user) |> MediaProxy.url()
+ avatar = User.avatar_url(user) |> MediaProxy.url()
+ avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
header = User.banner_url(user) |> MediaProxy.url()
+ header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
following_count =
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
@@ -240,17 +242,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
username: username_from_nickname(user.nickname),
acct: user.nickname,
display_name: display_name,
- locked: user.locked,
+ locked: user.is_locked,
created_at: Utils.to_masto_date(user.inserted_at),
followers_count: followers_count,
following_count: following_count,
statuses_count: user.note_count,
- note: user.bio || "",
+ note: user.bio,
url: user.uri || user.ap_id,
- avatar: image,
- avatar_static: image,
+ avatar: avatar,
+ avatar_static: avatar_static,
header: header,
- header_static: header,
+ header_static: header_static,
emojis: emojis,
fields: user.fields,
bot: bot,
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 3fe1967be..435bcde15 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
require Pleroma.Constants
alias Pleroma.Activity
- alias Pleroma.ActivityExpiration
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
@@ -56,23 +55,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end)
end
- def get_user(ap_id, fake_record_fallback \\ true) do
- cond do
- user = User.get_cached_by_ap_id(ap_id) ->
- user
-
- user = User.get_by_guessed_nickname(ap_id) ->
- user
-
- fake_record_fallback ->
- # TODO: refactor (fake records is never a good idea)
- User.error_user(ap_id)
-
- true ->
- nil
- end
- end
-
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
do: context_id
@@ -120,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
# Note: unresolved users are filtered out
actors =
(activities ++ parent_activities)
- |> Enum.map(&get_user(&1.data["actor"], false))
+ |> Enum.map(&CommonAPI.get_user(&1.data["actor"], false))
|> Enum.filter(& &1)
UserRelationship.view_relationships_option(reading_user, actors, subset: :source_mutes)
@@ -139,7 +121,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
"show.json",
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
) do
- user = get_user(activity.data["actor"])
+ user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
activity_object = Object.normalize(activity)
@@ -212,7 +194,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
object = Object.normalize(activity)
- user = get_user(activity.data["actor"])
+ user = CommonAPI.get_user(activity.data["actor"])
user_follower_address = user.follower_address
like_count = object.data["like_count"] || 0
@@ -245,8 +227,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
expires_at =
with true <- client_posted_this_activity,
- %ActivityExpiration{scheduled_at: scheduled_at} <-
- ActivityExpiration.get_by_activity_id(activity.id) do
+ %Oban.Job{scheduled_at: scheduled_at} <-
+ Pleroma.Workers.PurgeExpiredActivity.get_expiration(activity.id) do
scheduled_at
else
_ -> nil
@@ -266,7 +248,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reply_to = get_reply_to(activity, opts)
- reply_to_user = reply_to && get_user(reply_to.data["actor"])
+ reply_to_user = reply_to && CommonAPI.get_user(reply_to.data["actor"])
content =
object
@@ -433,6 +415,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
[attachment_url | _] = attachment["url"]
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"
href = attachment_url["href"] |> MediaProxy.url()
+ href_preview = attachment_url["href"] |> MediaProxy.preview_url()
type =
cond do
@@ -448,7 +431,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(attachment["id"] || hash_id),
url: href,
remote_url: href,
- preview_url: href,
+ preview_url: href_preview,
text_url: href,
type: type,
description: attachment["name"],
diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex
index cf923ded8..439cdd716 100644
--- a/lib/pleroma/web/mastodon_api/websocket_handler.ex
+++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex
@@ -23,8 +23,8 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
with params <- Enum.into(:cow_qs.parse_qs(qs), %{}),
sec_websocket <- :cowboy_req.header("sec-websocket-protocol", req, nil),
access_token <- Map.get(params, "access_token"),
- {:ok, user} <- authenticate_request(access_token, sec_websocket),
- {:ok, topic} <- Streamer.get_topic(Map.get(params, "stream"), user, params) do
+ {:ok, user, oauth_token} <- authenticate_request(access_token, sec_websocket),
+ {:ok, topic} <- Streamer.get_topic(params["stream"], user, oauth_token, params) do
req =
if sec_websocket do
:cowboy_req.set_resp_header("sec-websocket-protocol", sec_websocket, req)
@@ -117,7 +117,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
# Public streams without authentication.
defp authenticate_request(nil, nil) do
- {:ok, nil}
+ {:ok, nil, nil}
end
# Authenticated streams.
@@ -125,9 +125,9 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
token = access_token || sec_websocket
with true <- is_bitstring(token),
- %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
+ oauth_token = %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
user = %User{} <- User.get_cached_by_id(user_id) do
- {:ok, user}
+ {:ok, user, oauth_token}
else
_ -> {:error, :unauthorized}
end
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
index e18dd8224..8656b8cad 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.MediaProxy do
alias Pleroma.Config
+ alias Pleroma.Helpers.UriHelper
alias Pleroma.Upload
alias Pleroma.Web
alias Pleroma.Web.MediaProxy.Invalidation
@@ -40,27 +41,35 @@ defmodule Pleroma.Web.MediaProxy do
def url("/" <> _ = url), do: url
def url(url) do
- if disabled?() or not url_proxiable?(url) do
- url
- else
+ if enabled?() and url_proxiable?(url) do
encode_url(url)
+ else
+ url
end
end
@spec url_proxiable?(String.t()) :: boolean()
def url_proxiable?(url) do
- if local?(url) or whitelisted?(url) do
- false
+ not local?(url) and not whitelisted?(url)
+ end
+
+ def preview_url(url, preview_params \\ []) do
+ if preview_enabled?() do
+ encode_preview_url(url, preview_params)
else
- true
+ url(url)
end
end
- defp disabled?, do: !Config.get([:media_proxy, :enabled], false)
+ def enabled?, do: Config.get([:media_proxy, :enabled], false)
+
+ # Note: media proxy must be enabled for media preview proxy in order to load all
+ # non-local non-whitelisted URLs through it and be sure that body size constraint is preserved.
+ def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :enabled])
- defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
+ def local?(url), do: String.starts_with?(url, Pleroma.Web.base_url())
- defp whitelisted?(url) do
+ def whitelisted?(url) do
%{host: domain} = URI.parse(url)
mediaproxy_whitelist_domains =
@@ -85,17 +94,29 @@ defmodule Pleroma.Web.MediaProxy do
defp maybe_get_domain_from_url(domain), do: domain
- def encode_url(url) do
+ defp base64_sig64(url) do
base64 = Base.url_encode64(url, @base64_opts)
sig64 =
base64
- |> signed_url
+ |> signed_url()
|> Base.url_encode64(@base64_opts)
+ {base64, sig64}
+ end
+
+ def encode_url(url) do
+ {base64, sig64} = base64_sig64(url)
+
build_url(sig64, base64, filename(url))
end
+ def encode_preview_url(url, preview_params \\ []) do
+ {base64, sig64} = base64_sig64(url)
+
+ build_preview_url(sig64, base64, filename(url), preview_params)
+ end
+
def decode_url(sig, url) do
with {:ok, sig} <- Base.url_decode64(sig, @base64_opts),
signature when signature == sig <- signed_url(url) do
@@ -113,10 +134,14 @@ defmodule Pleroma.Web.MediaProxy do
if path = URI.parse(url_or_path).path, do: Path.basename(path)
end
- def build_url(sig_base64, url_base64, filename \\ nil) do
+ def base_url do
+ Config.get([:media_proxy, :base_url], Web.base_url())
+ end
+
+ defp proxy_url(path, sig_base64, url_base64, filename) do
[
- Config.get([:media_proxy, :base_url], Web.base_url()),
- "proxy",
+ base_url(),
+ path,
sig_base64,
url_base64,
filename
@@ -124,4 +149,38 @@ defmodule Pleroma.Web.MediaProxy do
|> Enum.filter(& &1)
|> Path.join()
end
+
+ def build_url(sig_base64, url_base64, filename \\ nil) do
+ proxy_url("proxy", sig_base64, url_base64, filename)
+ end
+
+ def build_preview_url(sig_base64, url_base64, filename \\ nil, preview_params \\ []) do
+ uri = proxy_url("proxy/preview", sig_base64, url_base64, filename)
+
+ UriHelper.modify_uri_params(uri, preview_params)
+ end
+
+ def verify_request_path_and_url(
+ %Plug.Conn{params: %{"filename" => _}, request_path: request_path},
+ url
+ ) do
+ verify_request_path_and_url(request_path, url)
+ end
+
+ def verify_request_path_and_url(request_path, url) when is_binary(request_path) do
+ filename = filename(url)
+
+ if filename && not basename_matches?(request_path, filename) do
+ {:wrong_filename, filename}
+ else
+ :ok
+ end
+ end
+
+ def verify_request_path_and_url(_, _), do: :ok
+
+ defp basename_matches?(path, filename) do
+ basename = Path.basename(path)
+ basename == filename or URI.decode(basename) == filename or URI.encode(basename) == filename
+ end
end
diff --git a/lib/pleroma/web/media_proxy/invalidation.ex b/lib/pleroma/web/media_proxy/invalidation.ex
index 5808861e6..4f4340478 100644
--- a/lib/pleroma/web/media_proxy/invalidation.ex
+++ b/lib/pleroma/web/media_proxy/invalidation.ex
@@ -33,6 +33,8 @@ defmodule Pleroma.Web.MediaProxy.Invalidation do
def prepare_urls(urls) do
urls
|> List.wrap()
- |> Enum.map(&MediaProxy.url/1)
+ |> Enum.map(fn url -> [MediaProxy.url(url), MediaProxy.preview_url(url)] end)
+ |> List.flatten()
+ |> Enum.uniq()
end
end
diff --git a/lib/pleroma/web/media_proxy/invalidations/http.ex b/lib/pleroma/web/media_proxy/invalidation/http.ex
index bb81d8888..0b0cde68c 100644
--- a/lib/pleroma/web/media_proxy/invalidations/http.ex
+++ b/lib/pleroma/web/media_proxy/invalidation/http.ex
@@ -30,7 +30,7 @@ defmodule Pleroma.Web.MediaProxy.Invalidation.Http do
{:ok, %{status: status} = env} when 400 <= status and status < 500 ->
{:error, env}
- {:error, error} = error ->
+ {:error, _} = error ->
error
_ ->
diff --git a/lib/pleroma/web/media_proxy/invalidations/script.ex b/lib/pleroma/web/media_proxy/invalidation/script.ex
index d32ffc50b..d32ffc50b 100644
--- a/lib/pleroma/web/media_proxy/invalidations/script.ex
+++ b/lib/pleroma/web/media_proxy/invalidation/script.ex
diff --git a/lib/pleroma/web/media_proxy/media_proxy_controller.ex b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
index 9a64b0ef3..90651ed9b 100644
--- a/lib/pleroma/web/media_proxy/media_proxy_controller.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy_controller.ex
@@ -5,44 +5,201 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyController do
use Pleroma.Web, :controller
+ alias Pleroma.Config
+ alias Pleroma.Helpers.MediaHelper
+ alias Pleroma.Helpers.UriHelper
alias Pleroma.ReverseProxy
alias Pleroma.Web.MediaProxy
+ alias Plug.Conn
- @default_proxy_opts [max_body_length: 25 * 1_048_576, http: [follow_redirect: true]]
-
- def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
- with config <- Pleroma.Config.get([:media_proxy], []),
- true <- Keyword.get(config, :enabled, false),
+ def remote(conn, %{"sig" => sig64, "url" => url64}) do
+ with {_, true} <- {:enabled, MediaProxy.enabled?()},
{:ok, url} <- MediaProxy.decode_url(sig64, url64),
{_, false} <- {:in_banned_urls, MediaProxy.in_banned_urls(url)},
- :ok <- filename_matches(params, conn.request_path, url) do
- ReverseProxy.call(conn, url, Keyword.get(config, :proxy_opts, @default_proxy_opts))
+ :ok <- MediaProxy.verify_request_path_and_url(conn, url) do
+ ReverseProxy.call(conn, url, media_proxy_opts())
else
- error when error in [false, {:in_banned_urls, true}] ->
- send_resp(conn, 404, Plug.Conn.Status.reason_phrase(404))
+ {:enabled, false} ->
+ send_resp(conn, 404, Conn.Status.reason_phrase(404))
+
+ {:in_banned_urls, true} ->
+ send_resp(conn, 404, Conn.Status.reason_phrase(404))
{:error, :invalid_signature} ->
- send_resp(conn, 403, Plug.Conn.Status.reason_phrase(403))
+ send_resp(conn, 403, Conn.Status.reason_phrase(403))
{:wrong_filename, filename} ->
redirect(conn, external: MediaProxy.build_url(sig64, url64, filename))
end
end
- def filename_matches(%{"filename" => _} = _, path, url) do
- filename = MediaProxy.filename(url)
+ def preview(%Conn{} = conn, %{"sig" => sig64, "url" => url64}) do
+ with {_, true} <- {:enabled, MediaProxy.preview_enabled?()},
+ {:ok, url} <- MediaProxy.decode_url(sig64, url64),
+ :ok <- MediaProxy.verify_request_path_and_url(conn, url) do
+ handle_preview(conn, url)
+ else
+ {:enabled, false} ->
+ send_resp(conn, 404, Conn.Status.reason_phrase(404))
+
+ {:error, :invalid_signature} ->
+ send_resp(conn, 403, Conn.Status.reason_phrase(403))
+
+ {:wrong_filename, filename} ->
+ redirect(conn, external: MediaProxy.build_preview_url(sig64, url64, filename))
+ end
+ end
+
+ defp handle_preview(conn, url) do
+ media_proxy_url = MediaProxy.url(url)
+
+ with {:ok, %{status: status} = head_response} when status in 200..299 <-
+ Pleroma.HTTP.request("head", media_proxy_url, [], [], pool: :media) do
+ content_type = Tesla.get_header(head_response, "content-type")
+ content_length = Tesla.get_header(head_response, "content-length")
+ content_length = content_length && String.to_integer(content_length)
+ static = conn.params["static"] in ["true", true]
+
+ cond do
+ static and content_type == "image/gif" ->
+ handle_jpeg_preview(conn, media_proxy_url)
+
+ static ->
+ drop_static_param_and_redirect(conn)
+
+ content_type == "image/gif" ->
+ redirect(conn, external: media_proxy_url)
+
+ min_content_length_for_preview() > 0 and content_length > 0 and
+ content_length < min_content_length_for_preview() ->
+ redirect(conn, external: media_proxy_url)
+
+ true ->
+ handle_preview(content_type, conn, media_proxy_url)
+ end
+ else
+ # If HEAD failed, redirecting to media proxy URI doesn't make much sense; returning an error
+ {_, %{status: status}} ->
+ send_resp(conn, :failed_dependency, "Can't fetch HTTP headers (HTTP #{status}).")
+
+ {:error, :recv_response_timeout} ->
+ send_resp(conn, :failed_dependency, "HEAD request timeout.")
+
+ _ ->
+ send_resp(conn, :failed_dependency, "Can't fetch HTTP headers.")
+ end
+ end
+
+ defp handle_preview("image/png" <> _ = _content_type, conn, media_proxy_url) do
+ handle_png_preview(conn, media_proxy_url)
+ end
+
+ defp handle_preview("image/" <> _ = _content_type, conn, media_proxy_url) do
+ handle_jpeg_preview(conn, media_proxy_url)
+ end
+
+ defp handle_preview("video/" <> _ = _content_type, conn, media_proxy_url) do
+ handle_video_preview(conn, media_proxy_url)
+ end
+
+ defp handle_preview(_unsupported_content_type, conn, media_proxy_url) do
+ fallback_on_preview_error(conn, media_proxy_url)
+ end
+
+ defp handle_png_preview(conn, media_proxy_url) do
+ quality = Config.get!([:media_preview_proxy, :image_quality])
+ {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions()
+
+ with {:ok, thumbnail_binary} <-
+ MediaHelper.image_resize(
+ media_proxy_url,
+ %{
+ max_width: thumbnail_max_width,
+ max_height: thumbnail_max_height,
+ quality: quality,
+ format: "png"
+ }
+ ) do
+ conn
+ |> put_preview_response_headers(["image/png", "preview.png"])
+ |> send_resp(200, thumbnail_binary)
+ else
+ _ ->
+ fallback_on_preview_error(conn, media_proxy_url)
+ end
+ end
+
+ defp handle_jpeg_preview(conn, media_proxy_url) do
+ quality = Config.get!([:media_preview_proxy, :image_quality])
+ {thumbnail_max_width, thumbnail_max_height} = thumbnail_max_dimensions()
- if filename && does_not_match(path, filename) do
- {:wrong_filename, filename}
+ with {:ok, thumbnail_binary} <-
+ MediaHelper.image_resize(
+ media_proxy_url,
+ %{max_width: thumbnail_max_width, max_height: thumbnail_max_height, quality: quality}
+ ) do
+ conn
+ |> put_preview_response_headers()
+ |> send_resp(200, thumbnail_binary)
else
- :ok
+ _ ->
+ fallback_on_preview_error(conn, media_proxy_url)
end
end
- def filename_matches(_, _, _), do: :ok
+ defp handle_video_preview(conn, media_proxy_url) do
+ with {:ok, thumbnail_binary} <-
+ MediaHelper.video_framegrab(media_proxy_url) do
+ conn
+ |> put_preview_response_headers()
+ |> send_resp(200, thumbnail_binary)
+ else
+ _ ->
+ fallback_on_preview_error(conn, media_proxy_url)
+ end
+ end
+
+ defp drop_static_param_and_redirect(conn) do
+ uri_without_static_param =
+ conn
+ |> current_url()
+ |> UriHelper.modify_uri_params(%{}, ["static"])
+
+ redirect(conn, external: uri_without_static_param)
+ end
+
+ defp fallback_on_preview_error(conn, media_proxy_url) do
+ redirect(conn, external: media_proxy_url)
+ end
+
+ defp put_preview_response_headers(
+ conn,
+ [content_type, filename] = _content_info \\ ["image/jpeg", "preview.jpg"]
+ ) do
+ conn
+ |> put_resp_header("content-type", content_type)
+ |> put_resp_header("content-disposition", "inline; filename=\"#{filename}\"")
+ |> put_resp_header("cache-control", ReverseProxy.default_cache_control_header())
+ end
+
+ defp thumbnail_max_dimensions do
+ config = media_preview_proxy_config()
+
+ thumbnail_max_width = Keyword.fetch!(config, :thumbnail_max_width)
+ thumbnail_max_height = Keyword.fetch!(config, :thumbnail_max_height)
+
+ {thumbnail_max_width, thumbnail_max_height}
+ end
+
+ defp min_content_length_for_preview do
+ Keyword.get(media_preview_proxy_config(), :min_content_length, 0)
+ end
+
+ defp media_preview_proxy_config do
+ Config.get!([:media_preview_proxy])
+ end
- defp does_not_match(path, filename) do
- basename = Path.basename(path)
- basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename
+ defp media_proxy_opts do
+ Config.get([:media_proxy, :proxy_opts], [])
end
end
diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex
index bd1459a17..bd1459a17 100644
--- a/lib/pleroma/web/metadata/feed.ex
+++ b/lib/pleroma/web/metadata/providers/feed.ex
diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
index 68c871e71..bb1b23208 100644
--- a/lib/pleroma/web/metadata/opengraph.ex
+++ b/lib/pleroma/web/metadata/providers/open_graph.ex
@@ -61,7 +61,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
@impl Provider
def build_tags(%{user: user}) do
- with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
+ with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
[
{:meta,
[
diff --git a/lib/pleroma/web/metadata/provider.ex b/lib/pleroma/web/metadata/providers/provider.ex
index 767288f9c..767288f9c 100644
--- a/lib/pleroma/web/metadata/provider.ex
+++ b/lib/pleroma/web/metadata/providers/provider.ex
diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex
index 8905c9c72..8905c9c72 100644
--- a/lib/pleroma/web/metadata/rel_me.ex
+++ b/lib/pleroma/web/metadata/providers/rel_me.ex
diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
index f15607896..a1dcb6e15 100644
--- a/lib/pleroma/web/metadata/restrict_indexing.ex
+++ b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
@@ -10,7 +10,9 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
"""
@impl true
- def build_tags(%{user: %{local: false}}) do
+ def build_tags(%{user: %{local: true, discoverable: true}}), do: []
+
+ def build_tags(_) do
[
{:meta,
[
@@ -19,7 +21,4 @@ defmodule Pleroma.Web.Metadata.Providers.RestrictIndexing do
], []}
]
end
-
- @impl true
- def build_tags(%{user: %{local: true}}), do: []
end
diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
index 5d08ce422..df34b033f 100644
--- a/lib/pleroma/web/metadata/twitter_card.ex
+++ b/lib/pleroma/web/metadata/providers/twitter_card.ex
@@ -40,7 +40,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
@impl Provider
def build_tags(%{user: user}) do
- with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do
+ with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
[
title_tag(user),
{:meta, [property: "twitter:description", content: truncated_bio], []},
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index 2f0dfb474..8a206e019 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -38,7 +38,7 @@ defmodule Pleroma.Web.Metadata.Utils do
def scrub_html(content), do: content
def attachment_url(url) do
- MediaProxy.url(url)
+ MediaProxy.preview_url(url)
end
def user_name_string(user) do
diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex
index 6cbbe8fd8..2a5c7c356 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex
@@ -5,10 +5,10 @@
defmodule Pleroma.Web.MongooseIM.MongooseIMController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.AuthenticationPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
+ alias Pleroma.Web.Plugs.RateLimiter
plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])
plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/o_auth.ex
index 2f1b8708d..2f1b8708d 100644
--- a/lib/pleroma/web/oauth.ex
+++ b/lib/pleroma/web/o_auth.ex
diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/o_auth/app.ex
index df99472e1..df99472e1 100644
--- a/lib/pleroma/web/oauth/app.ex
+++ b/lib/pleroma/web/o_auth/app.ex
diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex
index 268ee5b63..268ee5b63 100644
--- a/lib/pleroma/web/oauth/authorization.ex
+++ b/lib/pleroma/web/o_auth/authorization.ex
diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/o_auth/fallback_controller.ex
index a89ced886..a89ced886 100644
--- a/lib/pleroma/web/oauth/fallback_controller.ex
+++ b/lib/pleroma/web/o_auth/fallback_controller.ex
diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex
index f102c93e7..f102c93e7 100644
--- a/lib/pleroma/web/oauth/mfa_controller.ex
+++ b/lib/pleroma/web/o_auth/mfa_controller.ex
diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex
index 5d87db268..5d87db268 100644
--- a/lib/pleroma/web/oauth/mfa_view.ex
+++ b/lib/pleroma/web/o_auth/mfa_view.ex
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
index 06b116368..d2f9d1ceb 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Helpers.UriHelper
alias Pleroma.Maps
alias Pleroma.MFA
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
@@ -23,6 +22,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
+ alias Pleroma.Web.Plugs.RateLimiter
require Logger
@@ -31,7 +31,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
plug(:fetch_session)
plug(:fetch_flash)
- plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug])
+ plug(:skip_plug, [
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ ])
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
@@ -119,7 +122,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
redirect_uri = redirect_uri(conn, redirect_uri)
url_params = %{access_token: token.token}
url_params = Maps.put_if_present(url_params, :state, params["state"])
- url = UriHelper.append_uri_params(redirect_uri, url_params)
+ url = UriHelper.modify_uri_params(redirect_uri, url_params)
redirect(conn, external: url)
else
conn
@@ -161,7 +164,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
redirect_uri = redirect_uri(conn, redirect_uri)
url_params = %{code: auth.token}
url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"])
- url = UriHelper.append_uri_params(redirect_uri, url_params)
+ url = UriHelper.modify_uri_params(redirect_uri, url_params)
redirect(conn, external: url)
else
conn
@@ -200,7 +203,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:mfa_required, user, auth, _},
params
) do
- {:ok, token} = MFA.Token.create_token(user, auth)
+ {:ok, token} = MFA.Token.create(user, auth)
data = %{
"mfa_token" => token.token,
@@ -582,7 +585,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
do: put_session(conn, :registration_id, registration_id)
defp build_and_response_mfa_token(user, auth) do
- with {:ok, token} <- MFA.Token.create_token(user, auth) do
+ with {:ok, token} <- MFA.Token.create(user, auth) do
MFAView.render("mfa_response.json", %{token: token, user: user})
end
end
diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex
index f55247ebd..f55247ebd 100644
--- a/lib/pleroma/web/oauth/oauth_view.ex
+++ b/lib/pleroma/web/o_auth/o_auth_view.ex
diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/o_auth/scopes.ex
index 6f06f1431..90b9a0471 100644
--- a/lib/pleroma/web/oauth/scopes.ex
+++ b/lib/pleroma/web/o_auth/scopes.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.OAuth.Scopes do
Functions for dealing with scopes.
"""
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@doc """
Fetch scopes from request params.
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/o_auth/token.ex
index 08bb7326d..de37998f2 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/o_auth/token.ex
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.OAuth.Token do
true <- auth.app_id == app.id do
user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{}
- create_token(
+ create(
app,
user,
%{scopes: auth.scopes}
@@ -83,8 +83,22 @@ defmodule Pleroma.Web.OAuth.Token do
|> validate_required([:valid_until])
end
- @spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
- def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
+ @spec create(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
+ def create(%App{} = app, %User{} = user, attrs \\ %{}) do
+ with {:ok, token} <- do_create(app, user, attrs) do
+ if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do
+ Pleroma.Workers.PurgeExpiredToken.enqueue(%{
+ token_id: token.id,
+ valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
+ mod: __MODULE__
+ })
+ end
+
+ {:ok, token}
+ end
+ end
+
+ defp do_create(app, user, attrs) do
%__MODULE__{user_id: user.id, app_id: app.id}
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|> validate_required([:scopes, :app_id])
@@ -105,11 +119,6 @@ defmodule Pleroma.Web.OAuth.Token do
|> Repo.delete_all()
end
- def delete_expired_tokens do
- Query.get_expired_tokens()
- |> Repo.delete_all()
- end
-
def get_user_tokens(%User{id: user_id}) do
Query.get_by_user(user_id)
|> Query.preload([:app])
diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex
index 93d6e26ed..fd6d9b112 100644
--- a/lib/pleroma/web/oauth/token/query.ex
+++ b/lib/pleroma/web/o_auth/token/query.ex
@@ -33,12 +33,6 @@ defmodule Pleroma.Web.OAuth.Token.Query do
from(q in query, where: q.id == ^id)
end
- @spec get_expired_tokens(query, DateTime.t() | nil) :: query
- def get_expired_tokens(query \\ Token, date \\ nil) do
- expired_date = date || Timex.now()
- from(q in query, where: fragment("?", q.valid_until) < ^expired_date)
- end
-
@spec get_by_user(query, String.t()) :: query
def get_by_user(query \\ Token, user_id) do
from(q in query, where: q.user_id == ^user_id)
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
index debc29b0b..625b0fde2 100644
--- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
+++ b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
defp create_access_token({:error, error}, _), do: {:error, error}
defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
- Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
+ Token.create(app, user, add_refresh_token(token_params, token.refresh_token))
end
defp add_refresh_token(params, token) do
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
index 069c1ee21..069c1ee21 100644
--- a/lib/pleroma/web/oauth/token/strategy/revoke.ex
+++ b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex
index 43aeab6b0..43aeab6b0 100644
--- a/lib/pleroma/web/oauth/token/utils.ex
+++ b/lib/pleroma/web/o_auth/token/utils.ex
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
index de1b0b3f0..668ae0ea4 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/o_status/o_status_controller.ex
@@ -5,28 +5,24 @@
defmodule Pleroma.Web.OStatus.OStatusController do
use Pleroma.Web, :controller
- alias Fallback.RedirectController
alias Pleroma.Activity
alias Pleroma.Object
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Fallback.RedirectController
alias Pleroma.Web.Metadata.PlayerView
+ alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.Router
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.FederatingPlug.federating?/1
- )
-
plug(
RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
)
plug(
- Pleroma.Plugs.SetFormatPlug
+ Pleroma.Web.Plugs.SetFormatPlug
when action in [:object, :activity, :notice]
)
@@ -37,14 +33,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :object)
end
- def object(%{assigns: %{format: format}} = conn, _params) do
+ def object(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -59,13 +53,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
ActivityPubController.call(conn, :activity)
end
- def activity(%{assigns: %{format: format}} = conn, _params) do
+ def activity(conn, _params) do
with id <- Endpoint.url() <> conn.request_path,
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
+ redirect(conn, to: "/notice/#{activity.id}")
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
{:error, :not_found}
@@ -119,6 +111,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
def notice_player(conn, %{"id" => id}) do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.is_public?(activity),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%Object{} = object <- Object.normalize(activity),
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex
deleted file mode 100644
index e3aa4eb7e..000000000
--- a/lib/pleroma/web/oauth/token/clean_worker.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.CleanWorker do
- @moduledoc """
- The module represents functions to clean an expired OAuth and MFA tokens.
- """
- use GenServer
-
- @ten_seconds 10_000
- @one_day 86_400_000
-
- alias Pleroma.MFA
- alias Pleroma.Web.OAuth
- alias Pleroma.Workers.BackgroundWorker
-
- def start_link(_), do: GenServer.start_link(__MODULE__, %{})
-
- def init(_) do
- Process.send_after(self(), :perform, @ten_seconds)
- {:ok, nil}
- end
-
- @doc false
- def handle_info(:perform, state) do
- BackgroundWorker.enqueue("clean_expired_tokens", %{})
- interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
-
- Process.send_after(self(), :perform, interval)
- {:noreply, state}
- end
-
- def perform(:clean) do
- OAuth.Token.delete_expired_tokens()
- MFA.Token.delete_expired_tokens()
- end
-end
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
index 563edded7..30cf83567 100644
--- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -8,16 +8,21 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
require Pleroma.Constants
plug(
+ Majic.Plug,
+ [pool: Pleroma.MajicPool] when action in [:update_avatar, :update_background, :update_banner]
+ )
+
+ plug(
OpenApiSpex.Plug.PutApiSpec,
[module: Pleroma.Web.ApiSpec] when action == :confirmation_resend
)
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
index ea0921c77..6357148d0 100644
--- a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -4,17 +4,19 @@
defmodule Pleroma.Web.PleromaAPI.ChatController do
use Pleroma.Web, :controller
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
alias Pleroma.Activity
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.Object
alias Pleroma.Pagination
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
alias Pleroma.Web.PleromaAPI.ChatView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
import Ecto.Query
@@ -47,7 +49,7 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
}) do
with %MessageReference{} = cm_ref <-
MessageReference.get_by_id(message_id),
- ^chat_id <- cm_ref.chat_id |> to_string(),
+ ^chat_id <- to_string(cm_ref.chat_id),
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
{:ok, _} <- remove_or_delete(cm_ref, user) do
conn
@@ -68,18 +70,13 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
end
- defp remove_or_delete(cm_ref, _) do
- cm_ref
- |> MessageReference.delete()
- end
+ defp remove_or_delete(cm_ref, _), do: MessageReference.delete(cm_ref)
def post_chat_message(
- %{body_params: params, assigns: %{user: %{id: user_id} = user}} = conn,
- %{
- id: id
- }
+ %{body_params: params, assigns: %{user: user}} = conn,
+ %{id: id}
) do
- with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
%User{} = recipient <- User.get_cached_by_ap_id(chat.recipient),
{:ok, activity} <-
CommonAPI.post_chat_message(user, recipient, params[:content],
@@ -103,13 +100,12 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
end
- def mark_message_as_read(%{assigns: %{user: %{id: user_id}}} = conn, %{
- id: chat_id,
- message_id: message_id
- }) do
- with %MessageReference{} = cm_ref <-
- MessageReference.get_by_id(message_id),
- ^chat_id <- cm_ref.chat_id |> to_string(),
+ def mark_message_as_read(
+ %{assigns: %{user: %{id: user_id}}} = conn,
+ %{id: chat_id, message_id: message_id}
+ ) do
+ with %MessageReference{} = cm_ref <- MessageReference.get_by_id(message_id),
+ ^chat_id <- to_string(cm_ref.chat_id),
%Chat{user_id: ^user_id} <- Chat.get_by_id(chat_id),
{:ok, cm_ref} <- MessageReference.mark_as_read(cm_ref) do
conn
@@ -119,36 +115,28 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
def mark_as_read(
- %{
- body_params: %{last_read_id: last_read_id},
- assigns: %{user: %{id: user_id}}
- } = conn,
+ %{body_params: %{last_read_id: last_read_id}, assigns: %{user: user}} = conn,
%{id: id}
) do
- with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id),
- {_n, _} <-
- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
+ {_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
conn
|> put_view(ChatView)
|> render("show.json", chat: chat)
end
end
- def messages(%{assigns: %{user: %{id: user_id}}} = conn, %{id: id} = params) do
- with %Chat{} = chat <- Repo.get_by(Chat, id: id, user_id: user_id) do
- cm_refs =
+ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
+ chat_message_refs =
chat
|> MessageReference.for_chat_query()
|> Pagination.fetch_paginated(params)
conn
+ |> add_link_headers(chat_message_refs)
|> put_view(MessageReferenceView)
- |> render("index.json", chat_message_references: cm_refs)
- else
- _ ->
- conn
- |> put_status(:not_found)
- |> json(%{error: "not found"})
+ |> render("index.json", chat_message_references: chat_message_refs)
end
end
@@ -156,13 +144,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
blocked_ap_ids = User.blocked_users_ap_ids(user)
chats =
- from(c in Chat,
- where: c.user_id == ^user_id,
- where: c.recipient not in ^blocked_ap_ids,
- order_by: [desc: c.updated_at],
- inner_join: u in User,
- on: u.ap_id == c.recipient
- )
+ Chat.for_user_query(user_id)
+ |> where([c], c.recipient not in ^blocked_ap_ids)
|> Repo.all()
conn
@@ -170,8 +153,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|> render("index.json", chats: chats)
end
- def create(%{assigns: %{user: user}} = conn, params) do
- with %User{ap_id: recipient} <- User.get_by_id(params[:id]),
+ def create(%{assigns: %{user: user}} = conn, %{id: id}) do
+ with %User{ap_id: recipient} <- User.get_cached_by_id(id),
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
conn
|> put_view(ChatView)
@@ -179,8 +162,8 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
end
end
- def show(%{assigns: %{user: user}} = conn, params) do
- with %Chat{} = chat <- Repo.get_by(Chat, user_id: user.id, id: params[:id]) do
+ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
+ with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
conn
|> put_view(ChatView)
|> render("show.json", chat: chat)
diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
index 3d007f324..df52b7566 100644
--- a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
@@ -8,9 +8,9 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex
new file mode 100644
index 000000000..428c97de6
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex
@@ -0,0 +1,137 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Emoji.Pack
+ alias Pleroma.Web.ApiSpec
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ plug(
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ %{scopes: ["write"], admin: true}
+ when action in [
+ :create,
+ :update,
+ :delete
+ ]
+ )
+
+ defdelegate open_api_operation(action), to: ApiSpec.PleromaEmojiFileOperation
+
+ def create(%{body_params: params} = conn, %{name: pack_name}) do
+ filename = params[:filename] || get_filename(params[:file])
+ shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename))
+
+ with {:ok, pack} <- Pack.load_pack(pack_name),
+ {:ok, file} <- get_file(params[:file]),
+ {:ok, pack} <- Pack.add_file(pack, shortcode, filename, file) do
+ json(conn, pack.files)
+ else
+ {:error, :already_exists} ->
+ conn
+ |> put_status(:conflict)
+ |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})
+
+ {:error, :empty_values} ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: "pack name, shortcode or filename cannot be empty"})
+
+ {:error, _} = error ->
+ handle_error(conn, error, %{pack_name: pack_name})
+ end
+ end
+
+ def update(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: pack_name}) do
+ new_shortcode = params[:new_shortcode]
+ new_filename = params[:new_filename]
+ force = params[:force]
+
+ with {:ok, pack} <- Pack.load_pack(pack_name),
+ {:ok, pack} <- Pack.update_file(pack, shortcode, new_shortcode, new_filename, force) do
+ json(conn, pack.files)
+ else
+ {:error, :already_exists} ->
+ conn
+ |> put_status(:conflict)
+ |> json(%{
+ error:
+ "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
+ })
+
+ {:error, :empty_values} ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: "new_shortcode or new_filename cannot be empty"})
+
+ {:error, _} = error ->
+ handle_error(conn, error, %{pack_name: pack_name, code: shortcode})
+ end
+ end
+
+ def delete(conn, %{name: pack_name, shortcode: shortcode}) do
+ with {:ok, pack} <- Pack.load_pack(pack_name),
+ {:ok, pack} <- Pack.delete_file(pack, shortcode) do
+ json(conn, pack.files)
+ else
+ {:error, :empty_values} ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: "pack name or shortcode cannot be empty"})
+
+ {:error, _} = error ->
+ handle_error(conn, error, %{pack_name: pack_name, code: shortcode})
+ end
+ end
+
+ defp handle_error(conn, {:error, :doesnt_exist}, %{code: emoji_code}) do
+ conn
+ |> put_status(:bad_request)
+ |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"})
+ end
+
+ defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do
+ conn
+ |> put_status(:not_found)
+ |> json(%{error: "pack \"#{pack_name}\" is not found"})
+ end
+
+ defp handle_error(conn, {:error, _}, _) do
+ render_error(
+ conn,
+ :internal_server_error,
+ "Unexpected error occurred while adding file to pack."
+ )
+ end
+
+ defp get_filename(%Plug.Upload{filename: filename}), do: filename
+ defp get_filename(url) when is_binary(url), do: Path.basename(url)
+
+ def get_file(%Plug.Upload{} = file), do: {:ok, file}
+
+ def get_file(url) when is_binary(url) do
+ with {:ok, %Tesla.Env{body: body, status: code, headers: headers}}
+ when code in 200..299 <- Pleroma.HTTP.get(url) do
+ path = Plug.Upload.random_file!("emoji")
+
+ content_type =
+ case List.keyfind(headers, "content-type", 0) do
+ {"content-type", value} -> value
+ nil -> nil
+ end
+
+ File.write(path, body)
+
+ {:ok,
+ %Plug.Upload{
+ filename: Path.basename(url),
+ path: path,
+ content_type: content_type
+ }}
+ end
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
index 657f46324..a9accc5af 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
use Pleroma.Web, :controller
@@ -6,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
- Pleroma.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.OAuthScopesPlug,
%{scopes: ["write"], admin: true}
when action in [
:import_from_filesystem,
@@ -14,20 +18,21 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
:download,
:create,
:update,
- :delete,
- :add_file,
- :update_file,
- :delete_file
+ :delete
]
)
- @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
- plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive])
+ @skip_plugs [
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ ]
+ plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
- def remote(conn, %{url: url}) do
- with {:ok, packs} <- Pack.list_remote(url) do
+ def remote(conn, params) do
+ with {:ok, packs} <-
+ Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do
json(conn, packs)
else
{:error, :not_shareable} ->
@@ -184,105 +189,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
end
end
- def add_file(%{body_params: params} = conn, %{name: name}) do
- filename = params[:filename] || get_filename(params[:file])
- shortcode = params[:shortcode] || Path.basename(filename, Path.extname(filename))
-
- with {:ok, pack} <- Pack.add_file(name, shortcode, filename, params[:file]) do
- json(conn, pack.files)
- else
- {:error, :already_exists} ->
- conn
- |> put_status(:conflict)
- |> json(%{error: "An emoji with the \"#{shortcode}\" shortcode already exists"})
-
- {:error, :not_found} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "pack \"#{name}\" is not found"})
-
- {:error, :empty_values} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "pack name, shortcode or filename cannot be empty"})
-
- {:error, _} ->
- render_error(
- conn,
- :internal_server_error,
- "Unexpected error occurred while adding file to pack."
- )
- end
- end
-
- def update_file(%{body_params: %{shortcode: shortcode} = params} = conn, %{name: name}) do
- new_shortcode = params[:new_shortcode]
- new_filename = params[:new_filename]
- force = params[:force]
-
- with {:ok, pack} <- Pack.update_file(name, shortcode, new_shortcode, new_filename, force) do
- json(conn, pack.files)
- else
- {:error, :doesnt_exist} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
-
- {:error, :already_exists} ->
- conn
- |> put_status(:conflict)
- |> json(%{
- error:
- "New shortcode \"#{new_shortcode}\" is already used. If you want to override emoji use 'force' option"
- })
-
- {:error, :not_found} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "pack \"#{name}\" is not found"})
-
- {:error, :empty_values} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "new_shortcode or new_filename cannot be empty"})
-
- {:error, _} ->
- render_error(
- conn,
- :internal_server_error,
- "Unexpected error occurred while updating file in pack."
- )
- end
- end
-
- def delete_file(conn, %{name: name, shortcode: shortcode}) do
- with {:ok, pack} <- Pack.delete_file(name, shortcode) do
- json(conn, pack.files)
- else
- {:error, :doesnt_exist} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "Emoji \"#{shortcode}\" does not exist"})
-
- {:error, :not_found} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "pack \"#{name}\" is not found"})
-
- {:error, :empty_values} ->
- conn
- |> put_status(:bad_request)
- |> json(%{error: "pack name or shortcode cannot be empty"})
-
- {:error, _} ->
- render_error(
- conn,
- :internal_server_error,
- "Unexpected error occurred while removing file from pack."
- )
- end
- end
-
def import_from_filesystem(conn, _params) do
with {:ok, names} <- Pack.import_from_filesystem() do
json(conn, names)
@@ -298,7 +204,4 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
|> json(%{error: "Error accessing emoji pack directory"})
end
end
-
- defp get_filename(%Plug.Upload{filename: filename}), do: filename
- defp get_filename(url) when is_binary(url), do: Path.basename(url)
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
index 7f9254c13..ae199a50f 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
alias Pleroma.Activity
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete])
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
index df6c50ca5..15210f1e6 100644
--- a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -5,10 +5,11 @@
defmodule Pleroma.Web.PleromaAPI.MascotController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ plug(Majic.Plug, [pool: Pleroma.MajicPool] when action in [:update])
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show)
@@ -22,14 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
@doc "PUT /api/v1/pleroma/mascot"
def update(%{assigns: %{user: user}, body_params: %{file: file}} = conn, _) do
- with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
- # Reject if not an image
- %{type: "image"} = attachment <- render_attachment(object) do
+ with {:content_type, "image" <> _} <- {:content_type, file.content_type},
+ {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)) do
+ attachment = render_attachment(object)
{:ok, _user} = User.mascot_update(user, attachment)
json(conn, attachment)
else
- %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
+ {:content_type, _} ->
+ render_error(conn, :unsupported_media_type, "mascots can only be images")
end
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
index 3ed8bd294..fa32aaa84 100644
--- a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
@@ -6,10 +6,14 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do
use Pleroma.Web, :controller
alias Pleroma.Notification
- alias Pleroma.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :mark_as_read)
+
+ plug(
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ %{scopes: ["write:notifications"]} when action == :mark_as_read
+ )
+
plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation
diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
index e9a4fba92..632d65434 100644
--- a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
@@ -7,10 +7,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
index b86791d09..eba452300 100644
--- a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
@@ -10,8 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action in [:settings])
diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
new file mode 100644
index 000000000..7f089af1c
--- /dev/null
+++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
@@ -0,0 +1,61 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.UserImportController do
+ use Pleroma.Web, :controller
+
+ require Logger
+
+ alias Pleroma.User
+ alias Pleroma.Web.ApiSpec
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow)
+ plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks)
+ plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action == :mutes)
+
+ plug(OpenApiSpex.Plug.CastAndValidate)
+ defdelegate open_api_operation(action), to: ApiSpec.UserImportOperation
+
+ def follow(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do
+ follow(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{})
+ end
+
+ def follow(%{assigns: %{user: follower}, body_params: %{list: list}} = conn, _) do
+ identifiers =
+ list
+ |> String.split("\n")
+ |> Enum.map(&(&1 |> String.split(",") |> List.first()))
+ |> List.delete("Account address")
+ |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@")))
+ |> Enum.reject(&(&1 == ""))
+
+ User.Import.follow_import(follower, identifiers)
+ json(conn, "job started")
+ end
+
+ def blocks(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do
+ blocks(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{})
+ end
+
+ def blocks(%{assigns: %{user: blocker}, body_params: %{list: list}} = conn, _) do
+ User.Import.blocks_import(blocker, prepare_user_identifiers(list))
+ json(conn, "job started")
+ end
+
+ def mutes(%{body_params: %{list: %Plug.Upload{path: path}}} = conn, _) do
+ mutes(%Plug.Conn{conn | body_params: %{list: File.read!(path)}}, %{})
+ end
+
+ def mutes(%{assigns: %{user: user}, body_params: %{list: list}} = conn, _) do
+ User.Import.mutes_import(user, prepare_user_identifiers(list))
+ json(conn, "job started")
+ end
+
+ defp prepare_user_identifiers(list) do
+ list
+ |> String.split()
+ |> Enum.map(&String.trim_leading(&1, "@"))
+ end
+end
diff --git a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex
index bbff93abe..95bd4c368 100644
--- a/lib/pleroma/web/pleroma_api/views/scrobble_view.ex
+++ b/lib/pleroma/web/pleroma_api/views/scrobble_view.ex
@@ -10,14 +10,14 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleView do
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.Object
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
- alias Pleroma.Web.MastodonAPI.StatusView
def render("show.json", %{activity: %Activity{data: %{"type" => "Listen"}} = activity} = opts) do
object = Object.normalize(activity)
- user = StatusView.get_user(activity.data["actor"])
+ user = CommonAPI.get_user(activity.data["actor"])
created_at = Utils.to_masto_date(activity.data["published"])
%{
diff --git a/lib/pleroma/web/plug.ex b/lib/pleroma/web/plug.ex
new file mode 100644
index 000000000..840b35072
--- /dev/null
+++ b/lib/pleroma/web/plug.ex
@@ -0,0 +1,8 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plug do
+ # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug`
+ @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
+end
diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
new file mode 100644
index 000000000..d7d4e4092
--- /dev/null
+++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
+ import Plug.Conn
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
+
+ def init(options) do
+ options
+ end
+
+ def secret_token do
+ case Pleroma.Config.get(:admin_token) do
+ blank when blank in [nil, ""] -> nil
+ token -> token
+ end
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(conn, _) do
+ if secret_token() do
+ authenticate(conn)
+ else
+ conn
+ end
+ end
+
+ def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
+ if admin_token == secret_token() do
+ assign_admin_user(conn)
+ else
+ handle_bad_token(conn)
+ end
+ end
+
+ def authenticate(conn) do
+ token = secret_token()
+
+ case get_req_header(conn, "x-admin-token") do
+ blank when blank in [[], [""]] -> conn
+ [^token] -> assign_admin_user(conn)
+ _ -> handle_bad_token(conn)
+ end
+ end
+
+ defp assign_admin_user(conn) do
+ conn
+ |> assign(:user, %User{is_admin: true})
+ |> OAuthScopesPlug.skip_plug()
+ end
+
+ defp handle_bad_token(conn) do
+ RateLimiter.call(conn, name: :authentication)
+ end
+end
diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex
new file mode 100644
index 000000000..e2a8b1b69
--- /dev/null
+++ b/lib/pleroma/web/plugs/authentication_plug.ex
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.AuthenticationPlug do
+ alias Pleroma.User
+
+ import Plug.Conn
+
+ require Logger
+
+ def init(options), do: options
+
+ def checkpw(password, "$6" <> _ = password_hash) do
+ :crypt.crypt(password, password_hash) == password_hash
+ end
+
+ def checkpw(password, "$2" <> _ = password_hash) do
+ # Handle bcrypt passwords for Mastodon migration
+ Bcrypt.verify_pass(password, password_hash)
+ end
+
+ def checkpw(password, "$pbkdf2" <> _ = password_hash) do
+ Pbkdf2.verify_pass(password, password_hash)
+ end
+
+ def checkpw(_password, _password_hash) do
+ Logger.error("Password hash not recognized")
+ false
+ end
+
+ def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
+ do_update_password(user, password)
+ end
+
+ def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
+ do_update_password(user, password)
+ end
+
+ def maybe_update_password(user, _), do: {:ok, user}
+
+ defp do_update_password(user, password) do
+ user
+ |> User.password_update_changeset(%{
+ "password" => password,
+ "password_confirmation" => password
+ })
+ |> Pleroma.Repo.update()
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(
+ %{
+ assigns: %{
+ auth_user: %{password_hash: password_hash} = auth_user,
+ auth_credentials: %{password: password}
+ }
+ } = conn,
+ _
+ ) do
+ if checkpw(password, password_hash) do
+ {:ok, auth_user} = maybe_update_password(auth_user, password)
+
+ conn
+ |> assign(:user, auth_user)
+ |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
+ else
+ conn
+ end
+ end
+
+ def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do
+ Pbkdf2.no_user_verify()
+ conn
+ end
+
+ def call(conn, _), do: conn
+end
diff --git a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
new file mode 100644
index 000000000..4dadfb000
--- /dev/null
+++ b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _opts) do
+ with ["Basic " <> header] <- get_req_header(conn, "authorization"),
+ {:ok, userinfo} <- Base.decode64(header),
+ [username, password] <- String.split(userinfo, ":", parts: 2) do
+ conn
+ |> assign(:auth_credentials, %{
+ username: username,
+ password: password
+ })
+ else
+ _ -> conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex
new file mode 100644
index 000000000..6de01804a
--- /dev/null
+++ b/lib/pleroma/web/plugs/cache.ex
@@ -0,0 +1,136 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.Cache do
+ @moduledoc """
+ Caches successful GET responses.
+
+ To enable the cache add the plug to a router pipeline or controller:
+
+ plug(Pleroma.Web.Plugs.Cache)
+
+ ## Configuration
+
+ To configure the plug you need to pass settings as the second argument to the `plug/2` macro:
+
+ plug(Pleroma.Web.Plugs.Cache, [ttl: nil, query_params: true])
+
+ Available options:
+
+ - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`.
+ - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`.
+ - `tracking_fun`: A function that is called on successfull responses, no matter if the request is cached or not. It should accept a conn as the first argument and the value assigned to `tracking_fun_data` as the second.
+
+ Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct:
+
+ def index(conn, _params) do
+ ttl = 60_000 # one minute
+
+ conn
+ |> assign(:cache_ttl, ttl)
+ |> render("index.html")
+ end
+
+ """
+
+ import Phoenix.Controller, only: [current_path: 1, json: 2]
+ import Plug.Conn
+
+ @behaviour Plug
+
+ @defaults %{ttl: nil, query_params: true}
+
+ @impl true
+ def init([]), do: @defaults
+
+ def init(opts) do
+ opts = Map.new(opts)
+ Map.merge(@defaults, opts)
+ end
+
+ @impl true
+ def call(%{method: "GET"} = conn, opts) do
+ key = cache_key(conn, opts)
+
+ case Cachex.get(:web_resp_cache, key) do
+ {:ok, nil} ->
+ cache_resp(conn, opts)
+
+ {:ok, {content_type, body, tracking_fun_data}} ->
+ conn = opts.tracking_fun.(conn, tracking_fun_data)
+
+ send_cached(conn, {content_type, body})
+
+ {:ok, record} ->
+ send_cached(conn, record)
+
+ {atom, message} when atom in [:ignore, :error] ->
+ render_error(conn, message)
+ end
+ end
+
+ def call(conn, _), do: conn
+
+ # full path including query params
+ defp cache_key(conn, %{query_params: true}), do: current_path(conn)
+
+ # request path without query params
+ defp cache_key(conn, %{query_params: false}), do: conn.request_path
+
+ # request path with specific query params
+ defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do
+ query_string =
+ conn.params
+ |> Map.take(query_params)
+ |> URI.encode_query()
+
+ conn.request_path <> "?" <> query_string
+ end
+
+ defp cache_resp(conn, opts) do
+ register_before_send(conn, fn
+ %{status: 200, resp_body: body} = conn ->
+ ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl)
+ key = cache_key(conn, opts)
+ content_type = content_type(conn)
+
+ conn =
+ unless opts[:tracking_fun] do
+ Cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
+ conn
+ else
+ tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil)
+ Cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
+
+ opts.tracking_fun.(conn, tracking_fun_data)
+ end
+
+ put_resp_header(conn, "x-cache", "MISS from Pleroma")
+
+ conn ->
+ conn
+ end)
+ end
+
+ defp content_type(conn) do
+ conn
+ |> Plug.Conn.get_resp_header("content-type")
+ |> hd()
+ end
+
+ defp send_cached(conn, {content_type, body}) do
+ conn
+ |> put_resp_content_type(content_type, nil)
+ |> put_resp_header("x-cache", "HIT from Pleroma")
+ |> send_resp(:ok, body)
+ |> halt()
+ end
+
+ defp render_error(conn, message) do
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: message})
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/digest_plug.ex b/lib/pleroma/web/plugs/digest_plug.ex
new file mode 100644
index 000000000..b521b3073
--- /dev/null
+++ b/lib/pleroma/web/plugs/digest_plug.ex
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.DigestPlug do
+ alias Plug.Conn
+ require Logger
+
+ def read_body(conn, opts) do
+ {:ok, body, conn} = Conn.read_body(conn, opts)
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
+ {:ok, body, Conn.assign(conn, :digest, digest)}
+ end
+end
diff --git a/lib/pleroma/web/plugs/ensure_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex
new file mode 100644
index 000000000..ea2af6881
--- /dev/null
+++ b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do
+ import Plug.Conn
+ import Pleroma.Web.TranslationHelpers
+
+ alias Pleroma.User
+
+ use Pleroma.Web, :plug
+
+ def init(options) do
+ options
+ end
+
+ @impl true
+ def perform(
+ %{
+ assigns: %{
+ auth_credentials: %{password: _},
+ user: %User{multi_factor_authentication_settings: %{enabled: true}}
+ }
+ } = conn,
+ _
+ ) do
+ conn
+ |> render_error(:forbidden, "Two-factor authentication enabled, you must use a access token.")
+ |> halt()
+ end
+
+ def perform(%{assigns: %{user: %User{}}} = conn, _) do
+ conn
+ end
+
+ def perform(conn, _) do
+ conn
+ |> render_error(:forbidden, "Invalid credentials.")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex
new file mode 100644
index 000000000..3bebdac6d
--- /dev/null
+++ b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ use Pleroma.Web, :plug
+
+ def init(options) do
+ options
+ end
+
+ @impl true
+ def perform(conn, _) do
+ public? = Config.get!([:instance, :public])
+
+ case {public?, conn} do
+ {true, _} ->
+ conn
+
+ {false, %{assigns: %{user: %User{}}}} ->
+ conn
+
+ {false, _} ->
+ conn
+ |> render_error(:forbidden, "This resource requires authentication.")
+ |> halt
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex
new file mode 100644
index 000000000..70d3091f0
--- /dev/null
+++ b/lib/pleroma/web/plugs/ensure_user_key_plug.ex
@@ -0,0 +1,18 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do
+ import Plug.Conn
+
+ def init(opts) do
+ opts
+ end
+
+ def call(%{assigns: %{user: _}} = conn, _), do: conn
+
+ def call(conn, _) do
+ conn
+ |> assign(:user, nil)
+ end
+end
diff --git a/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex
new file mode 100644
index 000000000..0925ded4d
--- /dev/null
+++ b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug do
+ @moduledoc """
+ Marks `Pleroma.Web.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain.
+
+ No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).
+ """
+
+ use Pleroma.Web, :plug
+
+ def init(options), do: options
+
+ @impl true
+ def perform(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex
new file mode 100644
index 000000000..ace512a78
--- /dev/null
+++ b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug do
+ @moduledoc """
+ Marks `Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug
+ chain.
+
+ No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).
+ """
+
+ use Pleroma.Web, :plug
+
+ def init(options), do: options
+
+ @impl true
+ def perform(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/federating_plug.ex b/lib/pleroma/web/plugs/federating_plug.ex
new file mode 100644
index 000000000..3c90a7644
--- /dev/null
+++ b/lib/pleroma/web/plugs/federating_plug.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.FederatingPlug do
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _opts) do
+ if federating?() do
+ conn
+ else
+ fail(conn)
+ end
+ end
+
+ def federating?, do: Pleroma.Config.get([:instance, :federating])
+
+ # Definition for the use in :if_func / :unless_func plug options
+ def federating?(_conn), do: federating?()
+
+ defp fail(conn) do
+ conn
+ |> put_status(404)
+ |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
+ |> Phoenix.Controller.render("404.json")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
new file mode 100644
index 000000000..1b0b36813
--- /dev/null
+++ b/lib/pleroma/web/plugs/frontend_static.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.FrontendStatic do
+ require Pleroma.Constants
+
+ @moduledoc """
+ This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
+ """
+ @behaviour Plug
+
+ def file_path(path, frontend_type \\ :primary) do
+ if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
+ instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+
+ Path.join([
+ instance_static_path,
+ "frontends",
+ configuration["name"],
+ configuration["ref"],
+ path
+ ])
+ else
+ nil
+ end
+ end
+
+ def init(opts) do
+ opts
+ |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
+ |> Plug.Static.init()
+ |> Map.put(:frontend_type, opts[:frontend_type])
+ end
+
+ def call(conn, opts) do
+ with false <- invalid_path?(conn.path_info),
+ frontend_type <- Map.get(opts, :frontend_type, :primary),
+ path when not is_nil(path) <- file_path("", frontend_type) do
+ call_static(conn, opts, path)
+ else
+ _ ->
+ conn
+ end
+ end
+
+ defp invalid_path?(list) do
+ invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
+ end
+
+ defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
+ defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
+ defp invalid_path?([], _match), do: false
+
+ defp call_static(conn, opts, from) do
+ opts = Map.put(opts, :from, from)
+ Plug.Static.call(conn, opts)
+ end
+end
diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex
new file mode 100644
index 000000000..45aaf188e
--- /dev/null
+++ b/lib/pleroma/web/plugs/http_security_plug.ex
@@ -0,0 +1,225 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
+ alias Pleroma.Config
+ import Plug.Conn
+
+ require Logger
+
+ def init(opts), do: opts
+
+ def call(conn, _options) do
+ if Config.get([:http_security, :enabled]) do
+ conn
+ |> merge_resp_headers(headers())
+ |> maybe_send_sts_header(Config.get([:http_security, :sts]))
+ else
+ conn
+ end
+ end
+
+ defp headers do
+ referrer_policy = Config.get([:http_security, :referrer_policy])
+ report_uri = Config.get([:http_security, :report_uri])
+
+ headers = [
+ {"x-xss-protection", "1; mode=block"},
+ {"x-permitted-cross-domain-policies", "none"},
+ {"x-frame-options", "DENY"},
+ {"x-content-type-options", "nosniff"},
+ {"referrer-policy", referrer_policy},
+ {"x-download-options", "noopen"},
+ {"content-security-policy", csp_string()}
+ ]
+
+ if report_uri do
+ report_group = %{
+ "group" => "csp-endpoint",
+ "max-age" => 10_886_400,
+ "endpoints" => [
+ %{"url" => report_uri}
+ ]
+ }
+
+ [{"reply-to", Jason.encode!(report_group)} | headers]
+ else
+ headers
+ end
+ end
+
+ static_csp_rules = [
+ "default-src 'none'",
+ "base-uri 'self'",
+ "frame-ancestors 'none'",
+ "style-src 'self' 'unsafe-inline'",
+ "font-src 'self'",
+ "manifest-src 'self'"
+ ]
+
+ @csp_start [Enum.join(static_csp_rules, ";") <> ";"]
+
+ defp csp_string do
+ scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
+ static_url = Pleroma.Web.Endpoint.static_url()
+ websocket_url = Pleroma.Web.Endpoint.websocket_url()
+ report_uri = Config.get([:http_security, :report_uri])
+
+ img_src = "img-src 'self' data: blob:"
+ media_src = "media-src 'self'"
+
+ # Strict multimedia CSP enforcement only when MediaProxy is enabled
+ {img_src, media_src} =
+ if Config.get([:media_proxy, :enabled]) &&
+ !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
+ sources = build_csp_multimedia_source_list()
+ {[img_src, sources], [media_src, sources]}
+ else
+ {[img_src, " https:"], [media_src, " https:"]}
+ end
+
+ connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
+
+ connect_src =
+ if Config.get(:env) == :dev do
+ [connect_src, " http://localhost:3035/"]
+ else
+ connect_src
+ end
+
+ script_src =
+ if Config.get(:env) == :dev do
+ "script-src 'self' 'unsafe-eval'"
+ else
+ "script-src 'self'"
+ end
+
+ report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
+ insecure = if scheme == "https", do: "upgrade-insecure-requests"
+
+ @csp_start
+ |> add_csp_param(img_src)
+ |> add_csp_param(media_src)
+ |> add_csp_param(connect_src)
+ |> add_csp_param(script_src)
+ |> add_csp_param(insecure)
+ |> add_csp_param(report)
+ |> :erlang.iolist_to_binary()
+ end
+
+ defp build_csp_from_whitelist([], acc), do: acc
+
+ defp build_csp_from_whitelist([last], acc) do
+ [build_csp_param_from_whitelist(last) | acc]
+ end
+
+ defp build_csp_from_whitelist([head | tail], acc) do
+ build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
+ end
+
+ # TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
+ defp build_csp_param_from_whitelist("http" <> _ = url) do
+ build_csp_param(url)
+ end
+
+ defp build_csp_param_from_whitelist(url), do: url
+
+ defp build_csp_multimedia_source_list do
+ media_proxy_whitelist =
+ [:media_proxy, :whitelist]
+ |> Config.get()
+ |> build_csp_from_whitelist([])
+
+ captcha_method = Config.get([Pleroma.Captcha, :method])
+ captcha_endpoint = Config.get([captcha_method, :endpoint])
+
+ base_endpoints =
+ [
+ [:media_proxy, :base_url],
+ [Pleroma.Upload, :base_url],
+ [Pleroma.Uploaders.S3, :public_endpoint]
+ ]
+ |> Enum.map(&Config.get/1)
+
+ [captcha_endpoint | base_endpoints]
+ |> Enum.map(&build_csp_param/1)
+ |> Enum.reduce([], &add_source(&2, &1))
+ |> add_source(media_proxy_whitelist)
+ end
+
+ defp add_source(iodata, nil), do: iodata
+ defp add_source(iodata, []), do: iodata
+ defp add_source(iodata, source), do: [[?\s, source] | iodata]
+
+ defp add_csp_param(csp_iodata, nil), do: csp_iodata
+
+ defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
+
+ defp build_csp_param(nil), do: nil
+
+ defp build_csp_param(url) when is_binary(url) do
+ %{host: host, scheme: scheme} = URI.parse(url)
+
+ if scheme do
+ [scheme, "://", host]
+ end
+ end
+
+ def warn_if_disabled do
+ unless Config.get([:http_security, :enabled]) do
+ Logger.warn("
+ .i;;;;i.
+ iYcviii;vXY:
+ .YXi .i1c.
+ .YC. . in7.
+ .vc. ...... ;1c.
+ i7, .. .;1;
+ i7, .. ... .Y1i
+ ,7v .6MMM@; .YX,
+ .7;. ..IMMMMMM1 :t7.
+ .;Y. ;$MMMMMM9. :tc.
+ vY. .. .nMMM@MMU. ;1v.
+ i7i ... .#MM@M@C. .....:71i
+ it: .... $MMM@9;.,i;;;i,;tti
+ :t7. ..... 0MMMWv.,iii:::,,;St.
+ .nC. ..... IMMMQ..,::::::,.,czX.
+ .ct: ....... .ZMMMI..,:::::::,,:76Y.
+ c2: ......,i..Y$M@t..:::::::,,..inZY
+ vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
+ i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
+ iIS. ......:ii::..;@MI....,............;Ez.
+ .I9. ......:i::::...8M1..................C0z.
+ .z9; ......:i::::,.. .i:...................zWX.
+ vbv ......,i::::,,. ................. :AQY
+ c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
+ :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
+ :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
+ .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
+ 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
+ 7C...::::::::::::,,,,.. .................... vSi.
+ ;1;...,,::::::,......... .................. Yz:
+ v97,......... .voC.
+ izAotX7777777777777777777777777777777777777777Y7n92:
+ .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
+
+HTTP Security is disabled. Please re-enable it to prevent users from attacking
+your instance and your users via malicious posts:
+
+ config :pleroma, :http_security, enabled: true
+ ")
+ end
+ end
+
+ defp maybe_send_sts_header(conn, true) do
+ max_age_sts = Config.get([:http_security, :sts_max_age])
+ max_age_ct = Config.get([:http_security, :ct_max_age])
+
+ merge_resp_headers(conn, [
+ {"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
+ {"expect-ct", "enforce, max-age=#{max_age_ct}"}
+ ])
+ end
+
+ defp maybe_send_sts_header(conn, _), do: conn
+end
diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
new file mode 100644
index 000000000..036e2a773
--- /dev/null
+++ b/lib/pleroma/web/plugs/http_signature_plug.ex
@@ -0,0 +1,65 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
+ import Plug.Conn
+ import Phoenix.Controller, only: [get_format: 1, text: 2]
+ require Logger
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
+ conn
+ end
+
+ def call(conn, _opts) do
+ if get_format(conn) == "activity+json" do
+ conn
+ |> maybe_assign_valid_signature()
+ |> maybe_require_signature()
+ else
+ conn
+ end
+ end
+
+ defp maybe_assign_valid_signature(conn) do
+ if has_signature_header?(conn) do
+ # set (request-target) header to the appropriate value
+ # we also replace the digest header with the one we computed
+ request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
+
+ conn =
+ conn
+ |> put_req_header("(request-target)", request_target)
+ |> case do
+ %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
+ conn -> conn
+ end
+
+ assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
+ else
+ Logger.debug("No signature header!")
+ conn
+ end
+ end
+
+ defp has_signature_header?(conn) do
+ conn |> get_req_header("signature") |> Enum.at(0, false)
+ end
+
+ defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
+
+ defp maybe_require_signature(conn) do
+ if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
+ conn
+ |> put_status(:unauthorized)
+ |> text("Request not signed")
+ |> halt()
+ else
+ conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/idempotency_plug.ex b/lib/pleroma/web/plugs/idempotency_plug.ex
new file mode 100644
index 000000000..254a790b0
--- /dev/null
+++ b/lib/pleroma/web/plugs/idempotency_plug.ex
@@ -0,0 +1,84 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.IdempotencyPlug do
+ import Phoenix.Controller, only: [json: 2]
+ import Plug.Conn
+
+ @behaviour Plug
+
+ @impl true
+ def init(opts), do: opts
+
+ # Sending idempotency keys in `GET` and `DELETE` requests has no effect
+ # and should be avoided, as these requests are idempotent by definition.
+
+ @impl true
+ def call(%{method: method} = conn, _) when method in ["POST", "PUT", "PATCH"] do
+ case get_req_header(conn, "idempotency-key") do
+ [key] -> process_request(conn, key)
+ _ -> conn
+ end
+ end
+
+ def call(conn, _), do: conn
+
+ def process_request(conn, key) do
+ case Cachex.get(:idempotency_cache, key) do
+ {:ok, nil} ->
+ cache_resposnse(conn, key)
+
+ {:ok, record} ->
+ send_cached(conn, key, record)
+
+ {atom, message} when atom in [:ignore, :error] ->
+ render_error(conn, message)
+ end
+ end
+
+ defp cache_resposnse(conn, key) do
+ register_before_send(conn, fn conn ->
+ [request_id] = get_resp_header(conn, "x-request-id")
+ content_type = get_content_type(conn)
+
+ record = {request_id, content_type, conn.status, conn.resp_body}
+ {:ok, _} = Cachex.put(:idempotency_cache, key, record)
+
+ conn
+ |> put_resp_header("idempotency-key", key)
+ |> put_resp_header("x-original-request-id", request_id)
+ end)
+ end
+
+ defp send_cached(conn, key, record) do
+ {request_id, content_type, status, body} = record
+
+ conn
+ |> put_resp_header("idempotency-key", key)
+ |> put_resp_header("idempotent-replayed", "true")
+ |> put_resp_header("x-original-request-id", request_id)
+ |> put_resp_content_type(content_type)
+ |> send_resp(status, body)
+ |> halt()
+ end
+
+ defp render_error(conn, message) do
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: message})
+ |> halt()
+ end
+
+ defp get_content_type(conn) do
+ [content_type] = get_resp_header(conn, "content-type")
+
+ if String.contains?(content_type, ";") do
+ content_type
+ |> String.split(";")
+ |> hd()
+ else
+ content_type
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex
new file mode 100644
index 000000000..54b9175df
--- /dev/null
+++ b/lib/pleroma/web/plugs/instance_static.ex
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.InstanceStatic do
+ require Pleroma.Constants
+
+ @moduledoc """
+ This is a shim to call `Plug.Static` but with runtime `from` configuration.
+
+ Mountpoints are defined directly in the module to avoid calling the configuration for every request including non-static ones.
+ """
+ @behaviour Plug
+
+ def file_path(path) do
+ instance_path =
+ Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
+
+ frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary)
+
+ (File.exists?(instance_path) && instance_path) ||
+ (frontend_path && File.exists?(frontend_path) && frontend_path) ||
+ Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
+ end
+
+ def init(opts) do
+ opts
+ |> Keyword.put(:from, "__unconfigured_instance_static_plug")
+ |> Plug.Static.init()
+ end
+
+ for only <- Pleroma.Constants.static_only_files() do
+ def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
+ call_static(
+ conn,
+ opts,
+ Pleroma.Config.get([:instance, :static_dir], "instance/static")
+ )
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ end
+
+ defp call_static(conn, opts, from) do
+ opts =
+ opts
+ |> Map.put(:from, from)
+
+ Plug.Static.call(conn, opts)
+ end
+end
diff --git a/lib/pleroma/web/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex
new file mode 100644
index 000000000..2a54d0b59
--- /dev/null
+++ b/lib/pleroma/web/plugs/legacy_authentication_plug.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do
+ import Plug.Conn
+
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(
+ %{
+ assigns: %{
+ auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user,
+ auth_credentials: %{password: password}
+ }
+ } = conn,
+ _
+ ) do
+ with ^password_hash <- :crypt.crypt(password, password_hash),
+ {:ok, user} <-
+ User.reset_password(auth_user, %{password: password, password_confirmation: password}) do
+ conn
+ |> assign(:auth_user, user)
+ |> assign(:user, user)
+ |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
+ else
+ _ ->
+ conn
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
new file mode 100644
index 000000000..f44d4dee5
--- /dev/null
+++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
@@ -0,0 +1,71 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
+ alias Pleroma.Signature
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.Utils
+
+ import Plug.Conn
+ require Logger
+
+ def init(options), do: options
+
+ defp key_id_from_conn(conn) do
+ with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
+ {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
+ ap_id
+ else
+ _ ->
+ nil
+ end
+ end
+
+ defp user_from_key_id(conn) do
+ with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
+ {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
+ user
+ else
+ _ ->
+ nil
+ end
+ end
+
+ def call(%{assigns: %{user: _}} = conn, _opts), do: conn
+
+ # if this has payload make sure it is signed by the same actor that made it
+ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
+ with actor_id <- Utils.get_ap_id(actor),
+ {:user, %User{} = user} <- {:user, user_from_key_id(conn)},
+ {:user_match, true} <- {:user_match, user.ap_id == actor_id} do
+ assign(conn, :user, user)
+ else
+ {:user_match, false} ->
+ Logger.debug("Failed to map identity from signature (payload actor mismatch)")
+ Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
+ assign(conn, :valid_signature, false)
+
+ # remove me once testsuite uses mapped capabilities instead of what we do now
+ {:user, nil} ->
+ Logger.debug("Failed to map identity from signature (lookup failure)")
+ Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
+ conn
+ end
+ end
+
+ # no payload, probably a signed fetch
+ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
+ with %User{} = user <- user_from_key_id(conn) do
+ assign(conn, :user, user)
+ else
+ _ ->
+ Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
+ Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
+ assign(conn, :valid_signature, false)
+ end
+ end
+
+ # no signature at all
+ def call(conn, _opts), do: conn
+end
diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex
new file mode 100644
index 000000000..c7b58d90f
--- /dev/null
+++ b/lib/pleroma/web/plugs/o_auth_plug.ex
@@ -0,0 +1,120 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.OAuthPlug do
+ import Plug.Conn
+ import Ecto.Query
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Token
+
+ @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
+
+ def init(options), do: options
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(%{params: %{"access_token" => access_token}} = conn, _) do
+ with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:user, user)
+ else
+ _ ->
+ # token found, but maybe only with app
+ with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:app, app)
+ else
+ _ -> conn
+ end
+ end
+ end
+
+ def call(conn, _) do
+ case fetch_token_str(conn) do
+ {:ok, token} ->
+ with {:ok, user, token_record} <- fetch_user_and_token(token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:user, user)
+ else
+ _ ->
+ # token found, but maybe only with app
+ with {:ok, app, token_record} <- fetch_app_and_token(token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:app, app)
+ else
+ _ -> conn
+ end
+ end
+
+ _ ->
+ conn
+ end
+ end
+
+ # Gets user by token
+ #
+ @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil
+ defp fetch_user_and_token(token) do
+ query =
+ from(t in Token,
+ where: t.token == ^token,
+ join: user in assoc(t, :user),
+ preload: [user: user]
+ )
+
+ # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
+ with %Token{user: user} = token_record <- Repo.one(query) do
+ {:ok, user, token_record}
+ end
+ end
+
+ @spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil
+ defp fetch_app_and_token(token) do
+ query =
+ from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app])
+
+ with %Token{app: app} = token_record <- Repo.one(query) do
+ {:ok, app, token_record}
+ end
+ end
+
+ # Gets token from session by :oauth_token key
+ #
+ @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
+ defp fetch_token_from_session(conn) do
+ case get_session(conn, :oauth_token) do
+ nil -> :no_token_found
+ token -> {:ok, token}
+ end
+ end
+
+ # Gets token from headers
+ #
+ @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
+ defp fetch_token_str(%Plug.Conn{} = conn) do
+ headers = get_req_header(conn, "authorization")
+
+ with :no_token_found <- fetch_token_str(headers),
+ do: fetch_token_from_session(conn)
+ end
+
+ @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()}
+ defp fetch_token_str([]), do: :no_token_found
+
+ defp fetch_token_str([token | tail]) do
+ trimmed_token = String.trim(token)
+
+ case Regex.run(@realm_reg, trimmed_token) do
+ [_, match] -> {:ok, String.trim(match)}
+ _ -> fetch_token_str(tail)
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
new file mode 100644
index 000000000..cfc30837c
--- /dev/null
+++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
+ import Plug.Conn
+ import Pleroma.Web.Gettext
+
+ alias Pleroma.Config
+
+ use Pleroma.Web, :plug
+
+ def init(%{scopes: _} = options), do: options
+
+ @impl true
+ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
+ op = options[:op] || :|
+ token = assigns[:token]
+
+ scopes = transform_scopes(scopes, options)
+ matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
+
+ cond do
+ token && op == :| && Enum.any?(matched_scopes) ->
+ conn
+
+ token && op == :& && matched_scopes == scopes ->
+ conn
+
+ options[:fallback] == :proceed_unauthenticated ->
+ drop_auth_info(conn)
+
+ true ->
+ missing_scopes = scopes -- matched_scopes
+ permissions = Enum.join(missing_scopes, " #{op} ")
+
+ error_message =
+ dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions)
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(:forbidden, Jason.encode!(%{error: error_message}))
+ |> halt()
+ end
+ end
+
+ @doc "Drops authentication info from connection"
+ def drop_auth_info(conn) do
+ # To simplify debugging, setting a private variable on `conn` if auth info is dropped
+ conn
+ |> put_private(:authentication_ignored, true)
+ |> assign(:user, nil)
+ |> assign(:token, nil)
+ end
+
+ @doc "Keeps those of `scopes` which are descendants of `supported_scopes`"
+ def filter_descendants(scopes, supported_scopes) do
+ Enum.filter(
+ scopes,
+ fn scope ->
+ Enum.find(
+ supported_scopes,
+ &(scope == &1 || String.starts_with?(scope, &1 <> ":"))
+ )
+ end
+ )
+ end
+
+ @doc "Transforms scopes by applying supported options (e.g. :admin)"
+ def transform_scopes(scopes, options) do
+ if options[:admin] do
+ Config.oauth_admin_scopes(scopes)
+ else
+ scopes
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/plug_helper.ex b/lib/pleroma/web/plugs/plug_helper.ex
new file mode 100644
index 000000000..b314e7596
--- /dev/null
+++ b/lib/pleroma/web/plugs/plug_helper.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.PlugHelper do
+ @moduledoc "Pleroma Plug helper"
+
+ @called_plugs_list_id :called_plugs
+ def called_plugs_list_id, do: @called_plugs_list_id
+
+ @skipped_plugs_list_id :skipped_plugs
+ def skipped_plugs_list_id, do: @skipped_plugs_list_id
+
+ @doc "Returns `true` if specified plug was called."
+ def plug_called?(conn, plug_module) do
+ contained_in_private_list?(conn, @called_plugs_list_id, plug_module)
+ end
+
+ @doc "Returns `true` if specified plug was explicitly marked as skipped."
+ def plug_skipped?(conn, plug_module) do
+ contained_in_private_list?(conn, @skipped_plugs_list_id, plug_module)
+ end
+
+ @doc "Returns `true` if specified plug was either called or explicitly marked as skipped."
+ def plug_called_or_skipped?(conn, plug_module) do
+ plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
+ end
+
+ # Appends plug to known list (skipped, called). Intended to be used from within plug code only.
+ def append_to_private_list(conn, list_id, value) do
+ list = conn.private[list_id] || []
+ modified_list = Enum.uniq(list ++ [value])
+ Plug.Conn.put_private(conn, list_id, modified_list)
+ end
+
+ defp contained_in_private_list?(conn, private_variable, value) do
+ list = conn.private[private_variable] || []
+ value in list
+ end
+end
diff --git a/lib/pleroma/web/plugs/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex
new file mode 100644
index 000000000..a589610d1
--- /dev/null
+++ b/lib/pleroma/web/plugs/rate_limiter.ex
@@ -0,0 +1,267 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RateLimiter do
+ @moduledoc """
+
+ ## Configuration
+
+ A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration.
+ The basic configuration is a tuple where:
+
+ * The first element: `scale` (Integer). The time scale in milliseconds.
+ * The second element: `limit` (Integer). How many requests to limit in the time scale provided.
+
+ It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a
+ list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
+
+ To disable a limiter set its value to `nil`.
+
+ ### Example
+
+ config :pleroma, :rate_limit,
+ one: {1000, 10},
+ two: [{10_000, 10}, {10_000, 50}],
+ foobar: nil
+
+ Here we have three limiters:
+
+ * `one` which is not over 10req/1s
+ * `two` which has two limits: 10req/10s for unauthenticated users and 50req/10s for authenticated users
+ * `foobar` which is disabled
+
+ ## Usage
+
+ AllowedSyntax:
+
+ plug(Pleroma.Web.Plugs.RateLimiter, name: :limiter_name)
+ plug(Pleroma.Web.Plugs.RateLimiter, options) # :name is a required option
+
+ Allowed options:
+
+ * `name` required, always used to fetch the limit values from the config
+ * `bucket_name` overrides name for counting purposes (e.g. to have a separate limit for a set of actions)
+ * `params` appends values of specified request params (e.g. ["id"]) to bucket name
+
+ Inside a controller:
+
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :one] when action == :one)
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :two] when action in [:two, :three])
+
+ plug(
+ Pleroma.Web.Plugs.RateLimiter,
+ [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
+ when action in ~w(fav_status unfav_status)a
+ )
+
+ or inside a router pipeline:
+
+ pipeline :api do
+ ...
+ plug(Pleroma.Web.Plugs.RateLimiter, name: :one)
+ ...
+ end
+ """
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor
+
+ require Logger
+
+ @doc false
+ def init(plug_opts) do
+ plug_opts
+ end
+
+ def call(conn, plug_opts) do
+ if disabled?(conn) do
+ handle_disabled(conn)
+ else
+ action_settings = action_settings(plug_opts)
+ handle(conn, action_settings)
+ end
+ end
+
+ defp handle_disabled(conn) do
+ Logger.warn(
+ "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter."
+ )
+
+ conn
+ end
+
+ defp handle(conn, nil), do: conn
+
+ defp handle(conn, action_settings) do
+ action_settings
+ |> incorporate_conn_info(conn)
+ |> check_rate()
+ |> case do
+ {:ok, _count} ->
+ conn
+
+ {:error, _count} ->
+ render_throttled_error(conn)
+ end
+ end
+
+ def disabled?(conn) do
+ if Map.has_key?(conn.assigns, :remote_ip_found),
+ do: !conn.assigns.remote_ip_found,
+ else: false
+ end
+
+ @inspect_bucket_not_found {:error, :not_found}
+
+ def inspect_bucket(conn, bucket_name_root, plug_opts) do
+ with %{name: _} = action_settings <- action_settings(plug_opts) do
+ action_settings = incorporate_conn_info(action_settings, conn)
+ bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root})
+ key_name = make_key_name(action_settings)
+ limit = get_limits(action_settings)
+
+ case Cachex.get(bucket_name, key_name) do
+ {:error, :no_cache} ->
+ @inspect_bucket_not_found
+
+ {:ok, nil} ->
+ {0, limit}
+
+ {:ok, value} ->
+ {value, limit - value}
+ end
+ else
+ _ -> @inspect_bucket_not_found
+ end
+ end
+
+ def action_settings(plug_opts) do
+ with limiter_name when is_atom(limiter_name) <- plug_opts[:name],
+ limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do
+ bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name)
+
+ %{
+ name: bucket_name_root,
+ limits: limits,
+ opts: plug_opts
+ }
+ end
+ end
+
+ defp check_rate(action_settings) do
+ bucket_name = make_bucket_name(action_settings)
+ key_name = make_key_name(action_settings)
+ limit = get_limits(action_settings)
+
+ case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
+ {:commit, value} ->
+ {:ok, value}
+
+ {:ignore, value} ->
+ {:error, value}
+
+ {:error, :no_cache} ->
+ initialize_buckets!(action_settings)
+ check_rate(action_settings)
+ end
+ end
+
+ defp increment_value(nil, _limit), do: {:commit, 1}
+
+ defp increment_value(val, limit) when val >= limit, do: {:ignore, val}
+
+ defp increment_value(val, _limit), do: {:commit, val + 1}
+
+ defp incorporate_conn_info(action_settings, %{
+ assigns: %{user: %User{id: user_id}},
+ params: params
+ }) do
+ Map.merge(action_settings, %{
+ mode: :user,
+ conn_params: params,
+ conn_info: "#{user_id}"
+ })
+ end
+
+ defp incorporate_conn_info(action_settings, %{params: params} = conn) do
+ Map.merge(action_settings, %{
+ mode: :anon,
+ conn_params: params,
+ conn_info: "#{ip(conn)}"
+ })
+ end
+
+ defp ip(%{remote_ip: remote_ip}) do
+ remote_ip
+ |> Tuple.to_list()
+ |> Enum.join(".")
+ end
+
+ defp render_throttled_error(conn) do
+ conn
+ |> render_error(:too_many_requests, "Throttled")
+ |> halt()
+ end
+
+ defp make_key_name(action_settings) do
+ ""
+ |> attach_selected_params(action_settings)
+ |> attach_identity(action_settings)
+ end
+
+ defp get_scale(_, {scale, _}), do: scale
+
+ defp get_scale(:anon, [{scale, _}, {_, _}]), do: scale
+
+ defp get_scale(:user, [{_, _}, {scale, _}]), do: scale
+
+ defp get_limits(%{limits: {_scale, limit}}), do: limit
+
+ defp get_limits(%{mode: :user, limits: [_, {_, limit}]}), do: limit
+
+ defp get_limits(%{limits: [{_, limit}, _]}), do: limit
+
+ defp make_bucket_name(%{mode: :user, name: bucket_name_root}),
+ do: user_bucket_name(bucket_name_root)
+
+ defp make_bucket_name(%{mode: :anon, name: bucket_name_root}),
+ do: anon_bucket_name(bucket_name_root)
+
+ defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do
+ params_string =
+ plug_opts
+ |> Keyword.get(:params, [])
+ |> Enum.sort()
+ |> Enum.map(&Map.get(conn_params, &1, ""))
+ |> Enum.join(":")
+
+ [input, params_string]
+ |> Enum.join(":")
+ |> String.replace_leading(":", "")
+ end
+
+ defp initialize_buckets!(%{name: _name, limits: nil}), do: :ok
+
+ defp initialize_buckets!(%{name: name, limits: limits}) do
+ {:ok, _pid} =
+ LimiterSupervisor.add_or_return_limiter(anon_bucket_name(name), get_scale(:anon, limits))
+
+ {:ok, _pid} =
+ LimiterSupervisor.add_or_return_limiter(user_bucket_name(name), get_scale(:user, limits))
+
+ :ok
+ end
+
+ defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
+ do: "user:#{base}:#{conn_info}"
+
+ defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),
+ do: "ip:#{base}:#{conn_info}"
+
+ defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom()
+ defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom()
+end
diff --git a/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex
new file mode 100644
index 000000000..5642bb205
--- /dev/null
+++ b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor do
+ use DynamicSupervisor
+
+ import Cachex.Spec
+
+ def start_link(init_arg) do
+ DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
+ end
+
+ def add_or_return_limiter(limiter_name, expiration) do
+ result =
+ DynamicSupervisor.start_child(
+ __MODULE__,
+ %{
+ id: String.to_atom("rl_#{limiter_name}"),
+ start:
+ {Cachex, :start_link,
+ [
+ limiter_name,
+ [
+ expiration:
+ expiration(
+ default: expiration,
+ interval: check_interval(expiration),
+ lazy: true
+ )
+ ]
+ ]}
+ }
+ )
+
+ case result do
+ {:ok, _pid} = result -> result
+ {:error, {:already_started, pid}} -> {:ok, pid}
+ _ -> result
+ end
+ end
+
+ @impl true
+ def init(_init_arg) do
+ DynamicSupervisor.init(strategy: :one_for_one)
+ end
+
+ defp check_interval(exp) do
+ (exp / 2)
+ |> Kernel.trunc()
+ |> Kernel.min(5000)
+ |> Kernel.max(1)
+ end
+end
diff --git a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex
new file mode 100644
index 000000000..a1c84063d
--- /dev/null
+++ b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do
+ use Supervisor
+
+ def start_link(opts) do
+ Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
+ end
+
+ def init(_args) do
+ children = [
+ Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor
+ ]
+
+ opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor]
+ Supervisor.init(children, opts)
+ end
+end
diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex
new file mode 100644
index 000000000..401e2cbfa
--- /dev/null
+++ b/lib/pleroma/web/plugs/remote_ip.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RemoteIp do
+ @moduledoc """
+ This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
+ """
+
+ alias Pleroma.Config
+ import Plug.Conn
+
+ @behaviour Plug
+
+ def init(_), do: nil
+
+ def call(%{remote_ip: original_remote_ip} = conn, _) do
+ if Config.get([__MODULE__, :enabled]) do
+ %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts())
+ assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
+ else
+ conn
+ end
+ end
+
+ defp remote_ip_opts do
+ headers = Config.get([__MODULE__, :headers], []) |> MapSet.new()
+ reserved = Config.get([__MODULE__, :reserved], [])
+
+ proxies =
+ Config.get([__MODULE__, :proxies], [])
+ |> Enum.concat(reserved)
+ |> Enum.map(&maybe_add_cidr/1)
+
+ {headers, proxies}
+ end
+
+ defp maybe_add_cidr(proxy) when is_binary(proxy) do
+ proxy =
+ cond do
+ "/" in String.codepoints(proxy) -> proxy
+ InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32"
+ InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128"
+ end
+
+ InetCidr.parse(proxy, true)
+ end
+end
diff --git a/lib/pleroma/web/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex
new file mode 100644
index 000000000..6e176d553
--- /dev/null
+++ b/lib/pleroma/web/plugs/session_authentication_plug.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _) do
+ with saved_user_id <- get_session(conn, :user_id),
+ %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do
+ conn
+ |> assign(:user, conn.assigns.auth_user)
+ else
+ _ -> conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/set_format_plug.ex b/lib/pleroma/web/plugs/set_format_plug.ex
new file mode 100644
index 000000000..c16d2f81d
--- /dev/null
+++ b/lib/pleroma/web/plugs/set_format_plug.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetFormatPlug do
+ import Plug.Conn, only: [assign: 3, fetch_query_params: 1]
+
+ def init(_), do: nil
+
+ def call(conn, _) do
+ case get_format(conn) do
+ nil -> conn
+ format -> assign(conn, :format, format)
+ end
+ end
+
+ defp get_format(conn) do
+ conn.private[:phoenix_format] ||
+ case fetch_query_params(conn) do
+ %{query_params: %{"_format" => format}} -> format
+ _ -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex
new file mode 100644
index 000000000..d9d24b93f
--- /dev/null
+++ b/lib/pleroma/web/plugs/set_locale_plug.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# NOTE: this module is based on https://github.com/smeevil/set_locale
+defmodule Pleroma.Web.Plugs.SetLocalePlug do
+ import Plug.Conn, only: [get_req_header: 2, assign: 3]
+
+ def init(_), do: nil
+
+ def call(conn, _) do
+ locale = get_locale_from_header(conn) || Gettext.get_locale()
+ Gettext.put_locale(locale)
+ assign(conn, :locale, locale)
+ end
+
+ defp get_locale_from_header(conn) do
+ conn
+ |> extract_accept_language()
+ |> Enum.find(&supported_locale?/1)
+ end
+
+ defp extract_accept_language(conn) do
+ case get_req_header(conn, "accept-language") do
+ [value | _] ->
+ value
+ |> String.split(",")
+ |> Enum.map(&parse_language_option/1)
+ |> Enum.sort(&(&1.quality > &2.quality))
+ |> Enum.map(& &1.tag)
+ |> Enum.reject(&is_nil/1)
+ |> ensure_language_fallbacks()
+
+ _ ->
+ []
+ end
+ end
+
+ defp supported_locale?(locale) do
+ Pleroma.Web.Gettext
+ |> Gettext.known_locales()
+ |> Enum.member?(locale)
+ end
+
+ defp parse_language_option(string) do
+ captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
+
+ quality =
+ case Float.parse(captures["quality"] || "1.0") do
+ {val, _} -> val
+ :error -> 1.0
+ end
+
+ %{tag: captures["tag"], quality: quality}
+ end
+
+ defp ensure_language_fallbacks(tags) do
+ Enum.flat_map(tags, fn tag ->
+ [language | _] = String.split(tag, "-")
+ if Enum.member?(tags, language), do: [tag], else: [tag, language]
+ end)
+ end
+end
diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex
new file mode 100644
index 000000000..e520159e4
--- /dev/null
+++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do
+ import Plug.Conn
+ alias Pleroma.User
+
+ def init(opts) do
+ opts
+ end
+
+ def call(%{assigns: %{user: %User{id: id}}} = conn, _) do
+ conn
+ |> put_session(:user_id, id)
+ end
+
+ def call(conn, _), do: conn
+end
diff --git a/lib/pleroma/web/plugs/static_fe_plug.ex b/lib/pleroma/web/plugs/static_fe_plug.ex
new file mode 100644
index 000000000..658a1052e
--- /dev/null
+++ b/lib/pleroma/web/plugs/static_fe_plug.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.StaticFEPlug do
+ import Plug.Conn
+ alias Pleroma.Web.StaticFE.StaticFEController
+
+ def init(options), do: options
+
+ def call(conn, _) do
+ if enabled?() and requires_html?(conn) do
+ conn
+ |> StaticFEController.call(:show)
+ |> halt()
+ else
+ conn
+ end
+ end
+
+ defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
+
+ defp requires_html?(conn) do
+ Phoenix.Controller.get_format(conn) == "html"
+ end
+end
diff --git a/lib/pleroma/web/plugs/trailing_format_plug.ex b/lib/pleroma/web/plugs/trailing_format_plug.ex
new file mode 100644
index 000000000..e3f57c14a
--- /dev/null
+++ b/lib/pleroma/web/plugs/trailing_format_plug.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.TrailingFormatPlug do
+ @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."
+
+ @behaviour Plug
+ @paths [
+ "/api/statusnet",
+ "/api/statuses",
+ "/api/qvitter",
+ "/api/search",
+ "/api/account",
+ "/api/friends",
+ "/api/mutes",
+ "/api/media",
+ "/api/favorites",
+ "/api/blocks",
+ "/api/friendships",
+ "/api/users",
+ "/users",
+ "/nodeinfo",
+ "/api/help",
+ "/api/externalprofile",
+ "/notice",
+ "/api/pleroma/emoji",
+ "/api/oauth_tokens"
+ ]
+
+ def init(opts) do
+ TrailingFormatPlug.init(opts)
+ end
+
+ for path <- @paths do
+ def call(%{request_path: unquote(path) <> _} = conn, opts) do
+ TrailingFormatPlug.call(conn, opts)
+ end
+ end
+
+ def call(conn, _opts), do: conn
+end
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
new file mode 100644
index 000000000..402a8bb34
--- /dev/null
+++ b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UploadedMedia do
+ @moduledoc """
+ """
+
+ import Plug.Conn
+ import Pleroma.Web.Gettext
+ require Logger
+
+ alias Pleroma.Web.MediaProxy
+
+ @behaviour Plug
+ # no slashes
+ @path "media"
+
+ @default_cache_control_header "public, max-age=1209600"
+
+ def init(_opts) do
+ static_plug_opts =
+ [
+ headers: %{"cache-control" => @default_cache_control_header},
+ cache_control_for_etags: @default_cache_control_header
+ ]
+ |> Keyword.put(:from, "__unconfigured_media_plug")
+ |> Keyword.put(:at, "/__unconfigured_media_plug")
+ |> Plug.Static.init()
+
+ %{static_plug_opts: static_plug_opts}
+ end
+
+ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
+ conn =
+ case fetch_query_params(conn) do
+ %{query_params: %{"name" => name}} = conn ->
+ name = String.replace(name, "\"", "\\\"")
+
+ put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
+
+ conn ->
+ conn
+ end
+ |> merge_resp_headers([{"content-security-policy", "sandbox"}])
+
+ config = Pleroma.Config.get(Pleroma.Upload)
+
+ with uploader <- Keyword.fetch!(config, :uploader),
+ proxy_remote = Keyword.get(config, :proxy_remote, false),
+ {:ok, get_method} <- uploader.get_file(file),
+ false <- media_is_banned(conn, get_method) do
+ get_media(conn, get_method, proxy_remote, opts)
+ else
+ _ ->
+ conn
+ |> send_resp(:internal_server_error, dgettext("errors", "Failed"))
+ |> halt()
+ end
+ end
+
+ def call(conn, _opts), do: conn
+
+ defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
+ MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
+ end
+
+ defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
+
+ defp media_is_banned(_, _), do: false
+
+ defp get_media(conn, {:static_dir, directory}, _, opts) do
+ static_opts =
+ Map.get(opts, :static_plug_opts)
+ |> Map.put(:at, [@path])
+ |> Map.put(:from, directory)
+
+ conn = Plug.Static.call(conn, static_opts)
+
+ if conn.halted do
+ conn
+ else
+ conn
+ |> send_resp(:not_found, dgettext("errors", "Not found"))
+ |> halt()
+ end
+ end
+
+ defp get_media(conn, {:url, url}, true, _) do
+ conn
+ |> Pleroma.ReverseProxy.call(url, Pleroma.Config.get([Pleroma.Upload, :proxy_opts], []))
+ end
+
+ defp get_media(conn, {:url, url}, _, _) do
+ conn
+ |> Phoenix.Controller.redirect(external: url)
+ |> halt()
+ end
+
+ defp get_media(conn, unknown, _, _) do
+ Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
+
+ conn
+ |> send_resp(:internal_server_error, dgettext("errors", "Internal Error"))
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex
new file mode 100644
index 000000000..fa28ee48b
--- /dev/null
+++ b/lib/pleroma/web/plugs/user_enabled_plug.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserEnabledPlug do
+ import Plug.Conn
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{} = user}} = conn, _) do
+ case User.account_status(user) do
+ :active -> conn
+ _ -> assign(conn, :user, nil)
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex
new file mode 100644
index 000000000..4039600da
--- /dev/null
+++ b/lib/pleroma/web/plugs/user_fetcher_plug.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserFetcherPlug do
+ alias Pleroma.User
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _options) do
+ with %{auth_credentials: %{username: username}} <- conn.assigns,
+ %User{} = user <- User.get_by_nickname_or_email(username) do
+ assign(conn, :auth_user, user)
+ else
+ _ -> conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_is_admin_plug.ex b/lib/pleroma/web/plugs/user_is_admin_plug.ex
new file mode 100644
index 000000000..531c965f0
--- /dev/null
+++ b/lib/pleroma/web/plugs/user_is_admin_plug.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserIsAdminPlug do
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
+ conn
+ end
+
+ def call(conn, _) do
+ conn
+ |> render_error(:forbidden, "User is not an admin.")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/preload/instance.ex b/lib/pleroma/web/preload/providers/instance.ex
index 50d1f3382..a549bb1eb 100644
--- a/lib/pleroma/web/preload/instance.ex
+++ b/lib/pleroma/web/preload/providers/instance.ex
@@ -3,15 +3,17 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.Instance do
- alias Pleroma.Plugs.InstanceStatic
alias Pleroma.Web.MastodonAPI.InstanceView
alias Pleroma.Web.Nodeinfo.Nodeinfo
+ alias Pleroma.Web.Plugs.InstanceStatic
alias Pleroma.Web.Preload.Providers.Provider
+ alias Pleroma.Web.TwitterAPI.UtilView
@behaviour Provider
@instance_url "/api/v1/instance"
@panel_url "/instance/panel.html"
@nodeinfo_url "/nodeinfo/2.0.json"
+ @fe_config_url "/api/pleroma/frontend_configurations"
@impl Provider
def generate_terms(_params) do
@@ -19,6 +21,7 @@ defmodule Pleroma.Web.Preload.Providers.Instance do
|> build_info_tag()
|> build_panel_tag()
|> build_nodeinfo_tag()
+ |> build_fe_config_tag()
end
defp build_info_tag(acc) do
@@ -47,4 +50,10 @@ defmodule Pleroma.Web.Preload.Providers.Instance do
Map.put(acc, @nodeinfo_url, nodeinfo_data)
end
end
+
+ defp build_fe_config_tag(acc) do
+ fe_data = UtilView.render("frontend_configurations.json", %{})
+
+ Map.put(acc, @fe_config_url, fe_data)
+ end
end
diff --git a/lib/pleroma/web/preload/provider.ex b/lib/pleroma/web/preload/providers/provider.ex
index 7ef595a34..7ef595a34 100644
--- a/lib/pleroma/web/preload/provider.ex
+++ b/lib/pleroma/web/preload/providers/provider.ex
diff --git a/lib/pleroma/web/preload/timelines.ex b/lib/pleroma/web/preload/providers/timelines.ex
index b279a865d..b279a865d 100644
--- a/lib/pleroma/web/preload/timelines.ex
+++ b/lib/pleroma/web/preload/providers/timelines.ex
diff --git a/lib/pleroma/web/preload/user.ex b/lib/pleroma/web/preload/providers/user.ex
index b3d2e9b8d..b3d2e9b8d 100644
--- a/lib/pleroma/web/preload/user.ex
+++ b/lib/pleroma/web/preload/providers/user.ex
diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push.ex
index b80a6438d..b80a6438d 100644
--- a/lib/pleroma/web/push/push.ex
+++ b/lib/pleroma/web/push.ex
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 16368485e..da535aa68 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -19,7 +19,7 @@ defmodule Pleroma.Web.Push.Impl do
@types ["Create", "Follow", "Announce", "Like", "Move"]
@doc "Performs sending notifications for user subscriptions"
- @spec perform(Notification.t()) :: list(any) | :error
+ @spec perform(Notification.t()) :: list(any) | :error | {:error, :unknown_type}
def perform(
%{
activity: %{data: %{"type" => activity_type}} = activity,
@@ -64,20 +64,20 @@ defmodule Pleroma.Web.Push.Impl do
@doc "Push message to web"
def push_message(body, sub, api_key, subscription) do
case WebPushEncryption.send_web_push(body, sub, api_key) do
- {:ok, %{status_code: code}} when 400 <= code and code < 500 ->
+ {:ok, %{status: code}} when code in 400..499 ->
Logger.debug("Removing subscription record")
Repo.delete!(subscription)
:ok
- {:ok, %{status_code: code}} when 200 <= code and code < 300 ->
+ {:ok, %{status: code}} when code in 200..299 ->
:ok
- {:ok, %{status_code: code}} ->
+ {:ok, %{status: code}} ->
Logger.error("Web Push Notification failed with code: #{code}")
:error
- _ ->
- Logger.error("Web Push Notification failed with unknown error")
+ error ->
+ Logger.error("Web Push Notification failed with #{inspect(error)}")
:error
end
end
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index 8e2b51508..28f75b18d 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -5,7 +5,8 @@
defmodule Pleroma.Web.RelMe do
@options [
pool: :media,
- max_body: 2_000_000
+ max_body: 2_000_000,
+ recv_timeout: 2_000
]
if Pleroma.Config.get(:env) == :test do
@@ -23,18 +24,8 @@ defmodule Pleroma.Web.RelMe do
def parse(_), do: {:error, "No URL provided"}
defp parse_url(url) do
- opts =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.merge(@options,
- recv_timeout: 2_000,
- with_body: true
- )
- else
- @options
- end
-
with {:ok, %Tesla.Env{body: html, status: status}} when status in 200..299 <-
- Pleroma.HTTP.get(url, [], adapter: opts),
+ Pleroma.HTTP.get(url, [], @options),
{:ok, html_tree} <- Floki.parse_document(html),
data <-
Floki.attribute(html_tree, "link[rel~=me]", "href") ++
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index b7852c6e3..d67b594b5 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -9,14 +9,15 @@ defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.Object
alias Pleroma.Web.RichMedia.Parser
- @rich_media_options [
+ @options [
pool: :media,
- max_body: 2_000_000
+ max_body: 2_000_000,
+ recv_timeout: 2_000
]
@spec validate_page_url(URI.t() | binary()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
- validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld])
+ validate_tld = Config.get([Pleroma.Formatter, :validate_tld])
page_url
|> Linkify.Parser.url?(validate_tld: validate_tld)
@@ -56,7 +57,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do
def fetch_data_for_object(object) do
with true <- Config.get([:rich_media, :enabled]),
- false <- object.data["sensitive"] || false,
{:ok, page_url} <-
HTML.extract_first_external_url_from_object(object),
:ok <- validate_page_url(page_url),
@@ -86,18 +86,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do
def rich_media_get(url) do
headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}]
- options =
- if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
- Keyword.merge(@rich_media_options,
- recv_timeout: 2_000,
- with_body: true
- )
- else
- @rich_media_options
- end
-
head_check =
- case Pleroma.HTTP.head(url, headers, adapter: options) do
+ case Pleroma.HTTP.head(url, headers, @options) do
# If the HEAD request didn't reach the server for whatever reason,
# we assume the GET that comes right after won't either
{:error, _} = e ->
@@ -112,7 +102,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
:ok
end
- with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, adapter: options)
+ with :ok <- head_check, do: Pleroma.HTTP.get(url, headers, @options)
end
defp check_content_type(headers) do
@@ -128,7 +118,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do
end
end
- @max_body @rich_media_options[:max_body]
+ @max_body @options[:max_body]
defp check_content_length(headers) do
case List.keyfind(headers, "content-length", 0) do
{_, maybe_content_length} ->
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 569249f51..c70d2fdba 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -20,31 +20,61 @@ defmodule Pleroma.Web.RichMedia.Parser do
with {:ok, data} <- get_cached_or_parse(url),
{:ok, _} <- set_ttl_based_on_image(data, url) do
{:ok, data}
- else
- error ->
- Logger.error(fn -> "Rich media error: #{inspect(error)}" end)
end
end
defp get_cached_or_parse(url) do
- case Cachex.fetch!(:rich_media_cache, url, fn _ -> {:commit, parse_url(url)} end) do
- {:ok, _data} = res ->
- res
-
- {:error, :body_too_large} = e ->
- e
-
- {:error, {:content_type, _}} = e ->
- e
-
- # The TTL is not set for the errors above, since they are unlikely to change
- # with time
- {:error, _} = e ->
- ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
- Cachex.expire(:rich_media_cache, url, ttl)
- e
+ case Cachex.fetch(:rich_media_cache, url, fn ->
+ case parse_url(url) do
+ {:ok, _} = res ->
+ {:commit, res}
+
+ {:error, reason} = e ->
+ # Unfortunately we have to log errors here, instead of doing that
+ # along with ttl setting at the bottom. Otherwise we can get log spam
+ # if more than one process was waiting for the rich media card
+ # while it was generated. Ideally we would set ttl here as well,
+ # so we don't override it number_of_waiters_on_generation
+ # times, but one, obviously, can't set ttl for not-yet-created entry
+ # and Cachex doesn't support returning ttl from the fetch callback.
+ log_error(url, reason)
+ {:commit, e}
+ end
+ end) do
+ {action, res} when action in [:commit, :ok] ->
+ case res do
+ {:ok, _data} = res ->
+ res
+
+ {:error, reason} = e ->
+ if action == :commit, do: set_error_ttl(url, reason)
+ e
+ end
+
+ {:error, e} ->
+ {:error, {:cachex_error, e}}
end
end
+
+ defp set_error_ttl(_url, :body_too_large), do: :ok
+ defp set_error_ttl(_url, {:content_type, _}), do: :ok
+
+ # The TTL is not set for the errors above, since they are unlikely to change
+ # with time
+
+ defp set_error_ttl(url, _reason) do
+ ttl = Pleroma.Config.get([:rich_media, :failure_backoff], 60_000)
+ Cachex.expire(:rich_media_cache, url, ttl)
+ :ok
+ end
+
+ defp log_error(url, {:invalid_metadata, data}) do
+ Logger.debug(fn -> "Incomplete or invalid metadata for #{url}: #{inspect(data)}" end)
+ end
+
+ defp log_error(url, reason) do
+ Logger.warn(fn -> "Rich media error for #{url}: #{inspect(reason)}" end)
+ end
end
@doc """
@@ -98,7 +128,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
end)
end
- defp parse_url(url) do
+ def parse_url(url) do
with {:ok, %Tesla.Env{body: html}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url),
{:ok, html} <- Floki.parse_document(html) do
html
@@ -124,7 +154,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
defp check_parsed_data(data) do
- {:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
+ {:error, {:invalid_metadata, data}}
end
defp clean_parsed_data(data) do
diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex
new file mode 100644
index 000000000..8353f0fff
--- /dev/null
+++ b/lib/pleroma/web/rich_media/parser/ttl.ex
@@ -0,0 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parser.TTL do
+ @callback ttl(Map.t(), String.t()) :: Integer.t() | nil
+end
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
index c5aaea2d4..fc4ef79c0 100644
--- a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
+++ b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
@@ -1,7 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
@behaviour Pleroma.Web.RichMedia.Parser.TTL
- @impl Pleroma.Web.RichMedia.Parser.TTL
+ @impl true
def ttl(data, _url) do
image = Map.get(data, :image)
diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex
index 1fe6729c3..1fe6729c3 100644
--- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/o_embed.ex
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
deleted file mode 100644
index 6b3ec6d30..000000000
--- a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
+++ /dev/null
@@ -1,3 +0,0 @@
-defmodule Pleroma.Web.RichMedia.Parser.TTL do
- @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()}
-end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index c6433cc53..07a574f35 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -5,6 +5,26 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
+ pipeline :accepts_html do
+ plug(:accepts, ["html"])
+ end
+
+ pipeline :accepts_html_xml do
+ plug(:accepts, ["html", "xml", "rss", "atom"])
+ end
+
+ pipeline :accepts_html_json do
+ plug(:accepts, ["html", "activity+json", "json"])
+ end
+
+ pipeline :accepts_html_xml_json do
+ plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
+ end
+
+ pipeline :accepts_xml_rss_atom do
+ plug(:accepts, ["xml", "rss", "atom"])
+ end
+
pipeline :browser do
plug(:accepts, ["html"])
plug(:fetch_session)
@@ -12,31 +32,31 @@ defmodule Pleroma.Web.Router do
pipeline :oauth do
plug(:fetch_session)
- plug(Pleroma.Plugs.OAuthPlug)
- plug(Pleroma.Plugs.UserEnabledPlug)
+ plug(Pleroma.Web.Plugs.OAuthPlug)
+ plug(Pleroma.Web.Plugs.UserEnabledPlug)
end
pipeline :expect_authentication do
- plug(Pleroma.Plugs.ExpectAuthenticatedCheckPlug)
+ plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)
end
pipeline :expect_public_instance_or_authentication do
- plug(Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
+ plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
end
pipeline :authenticate do
- plug(Pleroma.Plugs.OAuthPlug)
- plug(Pleroma.Plugs.BasicAuthDecoderPlug)
- plug(Pleroma.Plugs.UserFetcherPlug)
- plug(Pleroma.Plugs.SessionAuthenticationPlug)
- plug(Pleroma.Plugs.LegacyAuthenticationPlug)
- plug(Pleroma.Plugs.AuthenticationPlug)
+ plug(Pleroma.Web.Plugs.OAuthPlug)
+ plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug)
+ plug(Pleroma.Web.Plugs.UserFetcherPlug)
+ plug(Pleroma.Web.Plugs.SessionAuthenticationPlug)
+ plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug)
+ plug(Pleroma.Web.Plugs.AuthenticationPlug)
end
pipeline :after_auth do
- plug(Pleroma.Plugs.UserEnabledPlug)
- plug(Pleroma.Plugs.SetUserSessionIdPlug)
- plug(Pleroma.Plugs.EnsureUserKeyPlug)
+ plug(Pleroma.Web.Plugs.UserEnabledPlug)
+ plug(Pleroma.Web.Plugs.SetUserSessionIdPlug)
+ plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
end
pipeline :base_api do
@@ -50,25 +70,25 @@ defmodule Pleroma.Web.Router do
plug(:expect_public_instance_or_authentication)
plug(:base_api)
plug(:after_auth)
- plug(Pleroma.Plugs.IdempotencyPlug)
+ plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :authenticated_api do
plug(:expect_authentication)
plug(:base_api)
plug(:after_auth)
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Plugs.IdempotencyPlug)
+ plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
+ plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :admin_api do
plug(:expect_authentication)
plug(:base_api)
- plug(Pleroma.Plugs.AdminSecretAuthenticationPlug)
+ plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth)
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Plugs.UserIsAdminPlug)
- plug(Pleroma.Plugs.IdempotencyPlug)
+ plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
+ plug(Pleroma.Web.Plugs.UserIsAdminPlug)
+ plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :mastodon_html do
@@ -80,7 +100,7 @@ defmodule Pleroma.Web.Router do
pipeline :pleroma_html do
plug(:browser)
plug(:authenticate)
- plug(Pleroma.Plugs.EnsureUserKeyPlug)
+ plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
end
pipeline :well_known do
@@ -178,9 +198,14 @@ defmodule Pleroma.Web.Router do
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
+ get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
+ get("/instance_document/:name", InstanceDocumentController, :show)
+ patch("/instance_document/:name", InstanceDocumentController, :update)
+ delete("/instance_document/:name", InstanceDocumentController, :delete)
+
patch("/users/confirm_email", AdminAPIController, :confirm_email)
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
@@ -214,9 +239,27 @@ defmodule Pleroma.Web.Router do
get("/media_proxy_caches", MediaProxyCacheController, :index)
post("/media_proxy_caches/delete", MediaProxyCacheController, :delete)
post("/media_proxy_caches/purge", MediaProxyCacheController, :purge)
+
+ get("/chats/:id", ChatController, :show)
+ get("/chats/:id/messages", ChatController, :messages)
+ delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
+ scope "/pack" do
+ pipe_through(:admin_api)
+
+ post("/", EmojiPackController, :create)
+ patch("/", EmojiPackController, :update)
+ delete("/", EmojiPackController, :delete)
+ end
+
+ scope "/pack" do
+ pipe_through(:api)
+
+ get("/", EmojiPackController, :show)
+ end
+
# Modifying packs
scope "/packs" do
pipe_through(:admin_api)
@@ -225,21 +268,17 @@ defmodule Pleroma.Web.Router do
get("/remote", EmojiPackController, :remote)
post("/download", EmojiPackController, :download)
- post("/:name", EmojiPackController, :create)
- patch("/:name", EmojiPackController, :update)
- delete("/:name", EmojiPackController, :delete)
-
- post("/:name/files", EmojiPackController, :add_file)
- patch("/:name/files", EmojiPackController, :update_file)
- delete("/:name/files", EmojiPackController, :delete_file)
+ post("/files", EmojiFileController, :create)
+ patch("/files", EmojiFileController, :update)
+ delete("/files", EmojiFileController, :delete)
end
# Pack info / downloading
scope "/packs" do
pipe_through(:api)
+
get("/", EmojiPackController, :index)
- get("/:name", EmojiPackController, :show)
- get("/:name/archive", EmojiPackController, :archive)
+ get("/archive", EmojiPackController, :archive)
end
end
@@ -260,14 +299,15 @@ defmodule Pleroma.Web.Router do
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
-
- post("/blocks_import", UtilController, :blocks_import)
- post("/follow_import", UtilController, :follow_import)
end
scope "/api/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:authenticated_api)
+ post("/mutes_import", UserImportController, :mutes)
+ post("/blocks_import", UserImportController, :blocks)
+ post("/follow_import", UserImportController, :follow)
+
get("/accounts/mfa", TwoFactorAuthenticationController, :settings)
get("/accounts/mfa/backup_codes", TwoFactorAuthenticationController, :backup_codes)
get("/accounts/mfa/setup/:method", TwoFactorAuthenticationController, :setup)
@@ -546,30 +586,43 @@ defmodule Pleroma.Web.Router do
)
end
- pipeline :ostatus do
- plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
- plug(Pleroma.Plugs.StaticFEPlug)
- end
-
- pipeline :oembed do
- plug(:accepts, ["json", "xml"])
- end
-
scope "/", Pleroma.Web do
- pipe_through([:ostatus, :http_signature])
+ # Note: html format is supported only if static FE is enabled
+ # Note: http signature is only considered for json requests (no auth for non-json requests)
+ pipe_through([:accepts_html_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
get("/objects/:uuid", OStatus.OStatusController, :object)
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
- get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
# Mastodon compatibility routes
get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
+ end
- get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+ scope "/", Pleroma.Web do
+ # Note: html format is supported only if static FE is enabled
+ # Note: http signature is only considered for json requests (no auth for non-json requests)
+ pipe_through([:accepts_html_xml_json, :http_signature, Pleroma.Web.Plugs.StaticFEPlug])
+
+ # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
+ end
+
+ scope "/", Pleroma.Web do
+ # Note: html format is supported only if static FE is enabled
+ pipe_through([:accepts_html_xml, Pleroma.Web.Plugs.StaticFEPlug])
+
+ get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed)
+ end
+
+ scope "/", Pleroma.Web do
+ pipe_through(:accepts_html)
+ get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
+ end
+ scope "/", Pleroma.Web do
+ pipe_through(:accepts_xml_rss_atom)
get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed)
end
@@ -670,6 +723,8 @@ defmodule Pleroma.Web.Router do
end
scope "/proxy/", Pleroma.Web.MediaProxy do
+ get("/preview/:sig/:url", MediaProxyController, :preview)
+ get("/preview/:sig/:url/:filename", MediaProxyController, :preview)
get("/:sig/:url", MediaProxyController, :remote)
get("/:sig/:url/:filename", MediaProxyController, :remote)
end
@@ -715,7 +770,7 @@ defmodule Pleroma.Web.Router do
get("/check_password", MongooseIMController, :check_password)
end
- scope "/", Fallback do
+ scope "/", Pleroma.Web.Fallback do
get("/registration/:token", RedirectController, :registration_page)
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
get("/api*path", RedirectController, :api_not_implemented)
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index a7a891b13..bdec0897a 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -17,74 +17,14 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id)
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.FederatingPlug.federating?/1
- )
-
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
- defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
- do: name
-
- defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
- do: summary
-
- defp get_title(_), do: nil
-
- defp not_found(conn, message) do
- conn
- |> put_status(404)
- |> render("error.html", %{message: message, meta: ""})
- end
-
- defp get_counts(%Activity{} = activity) do
- %Object{data: data} = Object.normalize(activity)
-
- %{
- likes: data["like_count"] || 0,
- replies: data["repliesCount"] || 0,
- announces: data["announcement_count"] || 0
- }
- end
-
- defp represent(%Activity{} = activity), do: represent(activity, false)
-
- defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
- {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
-
- link =
- case user.local do
- true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
- _ -> data["url"] || data["external_url"] || data["id"]
- end
-
- content =
- if data["content"] do
- data["content"]
- |> Pleroma.HTML.filter_tags()
- |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
- else
- nil
- end
-
- %{
- user: User.sanitize_html(user),
- title: get_title(activity.object),
- content: content,
- attachment: data["attachment"],
- link: link,
- published: data["published"],
- sensitive: data["sensitive"],
- selected: selected,
- counts: get_counts(activity),
- id: activity.id
- }
- end
-
+ @doc "Renders requested local public activity or public activities of requested user"
def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do
with %Activity{local: true} = activity <-
Activity.get_by_id_with_object(notice_id),
true <- Visibility.is_public?(activity.object),
+ {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)},
%User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do
meta = Metadata.build_tags(%{activity_id: notice_id, object: activity.object, user: user})
@@ -107,34 +47,35 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
- case User.get_cached_by_nickname_or_id(username_or_id) do
- %User{} = user ->
- meta = Metadata.build_tags(%{user: user})
-
- params =
- params
- |> Map.take(@page_keys)
- |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
-
- timeline =
- user
- |> ActivityPub.fetch_user_activities(nil, params)
- |> Enum.map(&represent/1)
-
- prev_page_id =
- (params["min_id"] || params["max_id"]) &&
- List.first(timeline) && List.first(timeline).id
-
- next_page_id = List.last(timeline) && List.last(timeline).id
-
- render(conn, "profile.html", %{
- user: User.sanitize_html(user),
- timeline: timeline,
- prev_page_id: prev_page_id,
- next_page_id: next_page_id,
- meta: meta
- })
+ with {_, %User{local: true} = user} <-
+ {:fetch_user, User.get_cached_by_nickname_or_id(username_or_id)},
+ {_, :visible} <- {:visibility, User.visible_for(user, _reading_user = nil)} do
+ meta = Metadata.build_tags(%{user: user})
+
+ params =
+ params
+ |> Map.take(@page_keys)
+ |> Map.new(fn {k, v} -> {String.to_existing_atom(k), v} end)
+ timeline =
+ user
+ |> ActivityPub.fetch_user_activities(_reading_user = nil, params)
+ |> Enum.map(&represent/1)
+
+ prev_page_id =
+ (params["min_id"] || params["max_id"]) &&
+ List.first(timeline) && List.first(timeline).id
+
+ next_page_id = List.last(timeline) && List.last(timeline).id
+
+ render(conn, "profile.html", %{
+ user: User.sanitize_html(user),
+ timeline: timeline,
+ prev_page_id: prev_page_id,
+ next_page_id: next_page_id,
+ meta: meta
+ })
+ else
_ ->
not_found(conn, "User not found.")
end
@@ -166,6 +107,64 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
end
end
+ defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
+ do: name
+
+ defp get_title(%Object{data: %{"summary" => summary}}) when is_binary(summary),
+ do: summary
+
+ defp get_title(_), do: nil
+
+ defp not_found(conn, message) do
+ conn
+ |> put_status(404)
+ |> render("error.html", %{message: message, meta: ""})
+ end
+
+ defp get_counts(%Activity{} = activity) do
+ %Object{data: data} = Object.normalize(activity)
+
+ %{
+ likes: data["like_count"] || 0,
+ replies: data["repliesCount"] || 0,
+ announces: data["announcement_count"] || 0
+ }
+ end
+
+ defp represent(%Activity{} = activity), do: represent(activity, false)
+
+ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
+ {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
+
+ link =
+ case user.local do
+ true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)
+ _ -> data["url"] || data["external_url"] || data["id"]
+ end
+
+ content =
+ if data["content"] do
+ data["content"]
+ |> Pleroma.HTML.filter_tags()
+ |> Pleroma.Emoji.Formatter.emojify(Map.get(data, "emoji", %{}))
+ else
+ nil
+ end
+
+ %{
+ user: User.sanitize_html(user),
+ title: get_title(activity.object),
+ content: content,
+ attachment: data["attachment"],
+ link: link,
+ published: data["published"],
+ sensitive: data["sensitive"],
+ selected: selected,
+ counts: get_counts(activity),
+ id: activity.id
+ }
+ end
+
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer.ex
index d1d70e556..d618dfe54 100644
--- a/lib/pleroma/web/streamer/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -15,6 +15,8 @@ defmodule Pleroma.Web.Streamer do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.StreamerView
@mix_env Mix.env()
@@ -26,53 +28,87 @@ defmodule Pleroma.Web.Streamer do
@user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
@doc "Expands and authorizes a stream, and registers the process for streaming."
- @spec get_topic_and_add_socket(stream :: String.t(), User.t() | nil, Map.t() | nil) ::
+ @spec get_topic_and_add_socket(
+ stream :: String.t(),
+ User.t() | nil,
+ Token.t() | nil,
+ Map.t() | nil
+ ) ::
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
- def get_topic_and_add_socket(stream, user, params \\ %{}) do
- case get_topic(stream, user, params) do
+ def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
+ case get_topic(stream, user, oauth_token, params) do
{:ok, topic} -> add_socket(topic, user)
error -> error
end
end
@doc "Expand and authorizes a stream"
- @spec get_topic(stream :: String.t(), User.t() | nil, Map.t()) ::
+ @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
{:ok, topic :: String.t()} | {:error, :bad_topic}
- def get_topic(stream, user, params \\ %{})
+ def get_topic(stream, user, oauth_token, params \\ %{})
# Allow all public steams.
- def get_topic(stream, _, _) when stream in @public_streams do
+ def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do
{:ok, stream}
end
# Allow all hashtags streams.
- def get_topic("hashtag", _, %{"tag" => tag}) do
+ def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
{:ok, "hashtag:" <> tag}
end
# Expand user streams.
- def get_topic(stream, %User{} = user, _) when stream in @user_streams do
- {:ok, stream <> ":" <> to_string(user.id)}
+ def get_topic(
+ stream,
+ %User{id: user_id} = user,
+ %Token{user_id: token_user_id} = oauth_token,
+ _params
+ )
+ when stream in @user_streams and user_id == token_user_id do
+ # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope)
+ required_scopes =
+ if stream == "user:notification" do
+ ["read:notifications"]
+ else
+ ["read:statuses"]
+ end
+
+ if OAuthScopesPlug.filter_descendants(required_scopes, oauth_token.scopes) == [] do
+ {:error, :unauthorized}
+ else
+ {:ok, stream <> ":" <> to_string(user.id)}
+ end
end
- def get_topic(stream, _, _) when stream in @user_streams do
+ def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams do
{:error, :unauthorized}
end
# List streams.
- def get_topic("list", %User{} = user, %{"list" => id}) do
- if Pleroma.List.get(id, user) do
- {:ok, "list:" <> to_string(id)}
- else
- {:error, :bad_topic}
+ def get_topic(
+ "list",
+ %User{id: user_id} = user,
+ %Token{user_id: token_user_id} = oauth_token,
+ %{"list" => id}
+ )
+ when user_id == token_user_id do
+ cond do
+ OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] ->
+ {:error, :unauthorized}
+
+ Pleroma.List.get(id, user) ->
+ {:ok, "list:" <> to_string(id)}
+
+ true ->
+ {:error, :bad_topic}
end
end
- def get_topic("list", _, _) do
+ def get_topic("list", _user, _oauth_token, _params) do
{:error, :unauthorized}
end
- def get_topic(_, _, _) do
+ def get_topic(_stream, _user, _oauth_token, _params) do
{:error, :bad_topic}
end
diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex
index 51603fe0c..3f28f1920 100644
--- a/lib/pleroma/web/templates/layout/app.html.eex
+++ b/lib/pleroma/web/templates/layout/app.html.eex
@@ -228,7 +228,7 @@
<body>
<div class="container">
<h1><%= Pleroma.Config.get([:instance, :name]) %></h1>
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>
</div>
</body>
</html>
diff --git a/lib/pleroma/web/templates/layout/email_styled.html.eex b/lib/pleroma/web/templates/layout/email_styled.html.eex
index ca2caaf4d..82cabd889 100644
--- a/lib/pleroma/web/templates/layout/email_styled.html.eex
+++ b/lib/pleroma/web/templates/layout/email_styled.html.eex
@@ -181,7 +181,7 @@
</div>
</div>
<% end %>
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>
</td>
</tr>
diff --git a/lib/pleroma/web/templates/layout/metadata_player.html.eex b/lib/pleroma/web/templates/layout/metadata_player.html.eex
index 460f28094..c00f6fa21 100644
--- a/lib/pleroma/web/templates/layout/metadata_player.html.eex
+++ b/lib/pleroma/web/templates/layout/metadata_player.html.eex
@@ -10,7 +10,7 @@ video, audio {
}
</style>
-<%= render @view_module, @view_template, assigns %>
+<%= @inner_content %>
</body>
</html>
diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex
index dc0ee2a5c..e6adb526b 100644
--- a/lib/pleroma/web/templates/layout/static_fe.html.eex
+++ b/lib/pleroma/web/templates/layout/static_fe.html.eex
@@ -9,7 +9,7 @@
</head>
<body>
<div class="container">
- <%= render @view_module, @view_template, assigns %>
+ <%= @inner_content %>
</div>
</body>
</html>
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/controller.ex
index c2de26b0b..f42dba442 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/controller.ex
@@ -6,10 +6,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
alias Pleroma.Notification
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.TwitterAPI.TokenView
require Logger
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
index 521dc9322..4480a4922 100644
--- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
alias Pleroma.Activity
alias Pleroma.MFA
alias Pleroma.Object.Fetcher
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.Auth.TOTPAuthenticator
@@ -18,11 +17,11 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
- plug(Pleroma.Web.FederatingPlug)
+ plug(Pleroma.Web.Plugs.FederatingPlug)
# Note: follower can submit the form (with password auth) not being signed in (having no token)
plug(
- OAuthScopesPlug,
+ Pleroma.Web.Plugs.OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
when action in [:do_follow]
)
@@ -135,7 +134,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
end
defp handle_follow_error(conn, {:mfa_required, followee, user, _} = _) do
- {:ok, %{token: token}} = MFA.Token.create_token(user)
+ {:ok, %{token: token}} = MFA.Token.create(user)
render(conn, "follow_mfa.html", %{followee: followee, mfa_token: token, error: false})
end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index f02c4075c..9ead0d626 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -11,20 +11,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Emoji
alias Pleroma.Healthcheck
alias Pleroma.Notification
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
- plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
-
- plug(
- OAuthScopesPlug,
- %{scopes: ["follow", "write:follows"]}
- when action == :follow_import
- )
-
- plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
+ plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
plug(
OAuthScopesPlug,
@@ -82,11 +74,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def frontend_configurations(conn, _params) do
- config =
- Config.get(:frontend_configurations, %{})
- |> Enum.into(%{})
-
- json(conn, config)
+ render(conn, "frontend_configurations.json")
end
def emoji(conn, _params) do
@@ -104,33 +92,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
- def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
- follow_import(conn, %{"list" => File.read!(listfile.path)})
- end
-
- def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
- followed_identifiers =
- list
- |> String.split("\n")
- |> Enum.map(&(&1 |> String.split(",") |> List.first()))
- |> List.delete("Account address")
- |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@")))
- |> Enum.reject(&(&1 == ""))
-
- User.follow_import(follower, followed_identifiers)
- json(conn, "job started")
- end
-
- def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
- blocks_import(conn, %{"list" => File.read!(listfile.path)})
- end
-
- def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
- blocked_identifiers = list |> String.split() |> Enum.map(&String.trim_leading(&1, "@"))
- User.blocks_import(blocker, blocked_identifiers)
- json(conn, "job started")
- end
-
def change_password(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
{:ok, user} ->
diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex
index d3bdb4f62..98eea1d18 100644
--- a/lib/pleroma/web/twitter_api/views/util_view.ex
+++ b/lib/pleroma/web/twitter_api/views/util_view.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.TwitterAPI.UtilView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
+ alias Pleroma.Config
alias Pleroma.Web
def status_net_config(instance) do
@@ -19,4 +20,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilView do
</config>
"""
end
+
+ def render("frontend_configurations.json", _) do
+ Config.get(:frontend_configurations, %{})
+ |> Enum.into(%{})
+ end
end
diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex
index 6b0fbe61e..bcdee6571 100644
--- a/lib/pleroma/web/views/email_view.ex
+++ b/lib/pleroma/web/views/email_view.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.EmailView do
use Pleroma.Web, :view
import Phoenix.HTML
diff --git a/lib/pleroma/web/views/mailer/subscription_view.ex b/lib/pleroma/web/views/mailer/subscription_view.ex
index fc3d20816..4562a9d6c 100644
--- a/lib/pleroma/web/views/mailer/subscription_view.ex
+++ b/lib/pleroma/web/views/mailer/subscription_view.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Mailer.SubscriptionView do
use Pleroma.Web, :view
end
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
deleted file mode 100644
index 4f9281851..000000000
--- a/lib/pleroma/web/web.ex
+++ /dev/null
@@ -1,239 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plug do
- # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug`
- @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
-end
-
-defmodule Pleroma.Web do
- @moduledoc """
- A module that keeps using definitions for controllers,
- views and so on.
-
- This can be used in your application as:
-
- use Pleroma.Web, :controller
- use Pleroma.Web, :view
-
- The definitions below will be executed for every view,
- controller, etc, so keep them short and clean, focused
- on imports, uses and aliases.
-
- Do NOT define functions inside the quoted expressions
- below.
- """
-
- alias Pleroma.Plugs.EnsureAuthenticatedPlug
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug
- alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.PlugHelper
-
- def controller do
- quote do
- use Phoenix.Controller, namespace: Pleroma.Web
-
- import Plug.Conn
-
- import Pleroma.Web.Gettext
- import Pleroma.Web.Router.Helpers
- import Pleroma.Web.TranslationHelpers
-
- plug(:set_put_layout)
-
- defp set_put_layout(conn, _) do
- put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
- end
-
- # Marks plugs intentionally skipped and blocks their execution if present in plugs chain
- defp skip_plug(conn, plug_modules) do
- plug_modules
- |> List.wrap()
- |> Enum.reduce(
- conn,
- fn plug_module, conn ->
- try do
- plug_module.skip_plug(conn)
- rescue
- UndefinedFunctionError ->
- raise "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code."
- end
- end
- )
- end
-
- # Executed just before actual controller action, invokes before-action hooks (callbacks)
- defp action(conn, params) do
- with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn),
- %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn),
- %{halted: false} = conn <- maybe_perform_authenticated_check(conn),
- %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do
- super(conn, params)
- end
- end
-
- # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored
- # (neither performed nor explicitly skipped)
- defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
- if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
- not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
- OAuthScopesPlug.drop_auth_info(conn)
- else
- conn
- end
- end
-
- # Ensures instance is public -or- user is authenticated if such check was scheduled
- defp maybe_perform_public_or_authenticated_check(conn) do
- if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do
- EnsurePublicOrAuthenticatedPlug.call(conn, %{})
- else
- conn
- end
- end
-
- # Ensures user is authenticated if such check was scheduled
- # Note: runs prior to action even if it was already executed earlier in plug chain
- # (since OAuthScopesPlug has option of proceeding unauthenticated)
- defp maybe_perform_authenticated_check(conn) do
- if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do
- EnsureAuthenticatedPlug.call(conn, %{})
- else
- conn
- end
- end
-
- # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
- defp maybe_halt_on_missing_oauth_scopes_check(conn) do
- if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
- not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
- conn
- |> render_error(
- :forbidden,
- "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
- )
- |> halt()
- else
- conn
- end
- end
- end
- end
-
- def view do
- quote do
- use Phoenix.View,
- root: "lib/pleroma/web/templates",
- namespace: Pleroma.Web
-
- # Import convenience functions from controllers
- import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
-
- import Pleroma.Web.ErrorHelpers
- import Pleroma.Web.Gettext
- import Pleroma.Web.Router.Helpers
-
- require Logger
-
- @doc "Same as `render/3` but wrapped in a rescue block"
- def safe_render(view, template, assigns \\ %{}) do
- Phoenix.View.render(view, template, assigns)
- rescue
- error ->
- Logger.error(
- "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
- Exception.format(:error, error, __STACKTRACE__)
- )
-
- nil
- end
-
- @doc """
- Same as `render_many/4` but wrapped in rescue block.
- """
- def safe_render_many(collection, view, template, assigns \\ %{}) do
- Enum.map(collection, fn resource ->
- as = Map.get(assigns, :as) || view.__resource__
- assigns = Map.put(assigns, as, resource)
- safe_render(view, template, assigns)
- end)
- |> Enum.filter(& &1)
- end
- end
- end
-
- def router do
- quote do
- use Phoenix.Router
- # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
- import Plug.Conn
- import Phoenix.Controller
- end
- end
-
- def channel do
- quote do
- # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
- use Phoenix.Channel
- import Pleroma.Web.Gettext
- end
- end
-
- def plug do
- quote do
- @behaviour Pleroma.Web.Plug
- @behaviour Plug
-
- @doc """
- Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
- """
- def skip_plug(conn) do
- PlugHelper.append_to_private_list(
- conn,
- PlugHelper.skipped_plugs_list_id(),
- __MODULE__
- )
- end
-
- @impl Plug
- @doc """
- Before-plug hook that
- * ensures the plug is not skipped
- * processes `:if_func` / `:unless_func` functional pre-run conditions
- * adds plug to the list of called plugs and calls `perform/2` if checks are passed
-
- Note: multiple invocations of the same plug (with different or same options) are allowed.
- """
- def call(%Plug.Conn{} = conn, options) do
- if PlugHelper.plug_skipped?(conn, __MODULE__) ||
- (options[:if_func] && !options[:if_func].(conn)) ||
- (options[:unless_func] && options[:unless_func].(conn)) do
- conn
- else
- conn =
- PlugHelper.append_to_private_list(
- conn,
- PlugHelper.called_plugs_list_id(),
- __MODULE__
- )
-
- apply(__MODULE__, :perform, [conn, options])
- end
- end
- end
- end
-
- @doc """
- When used, dispatch to the appropriate controller/view/etc.
- """
- defmacro __using__(which) when is_atom(which) do
- apply(__MODULE__, which, [])
- end
-
- def base_url do
- Pleroma.Web.Endpoint.url()
- end
-end
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger.ex
index 6629f5356..6629f5356 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger.ex
diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex
index 7077b20d2..9f0938fc0 100644
--- a/lib/pleroma/web/web_finger/web_finger_controller.ex
+++ b/lib/pleroma/web/web_finger/web_finger_controller.ex
@@ -7,8 +7,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
alias Pleroma.Web.WebFinger
- plug(Pleroma.Plugs.SetFormatPlug)
- plug(Pleroma.Web.FederatingPlug)
+ plug(Pleroma.Web.Plugs.SetFormatPlug)
+ plug(Pleroma.Web.Plugs.FederatingPlug)
def host_meta(conn, _params) do
xml = WebFinger.host_meta()
diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml.ex
index c69a86a1e..c69a86a1e 100644
--- a/lib/pleroma/web/xml/xml.ex
+++ b/lib/pleroma/web/xml.ex