aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorMaxim Filippov <colixer@gmail.com>2019-07-24 02:42:28 +0300
committerMaxim Filippov <colixer@gmail.com>2019-07-24 02:42:28 +0300
commitf46805bb40bf29cd67acb33b6b65ed1e4e28d3e1 (patch)
tree7542dbbe178183e4dd3c3451aa7744f18117fa37 /lib
parent03471151d6089e318abaf5265d42ffedf7a5b902 (diff)
parent1a751529fb2cd3929e3373a908bec5db5cc32f1b (diff)
downloadpleroma-f46805bb40bf29cd67acb33b6b65ed1e4e28d3e1.tar.gz
Merge branch 'develop' into feature/admin-api-user-statuses
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/user.ex18
-rw-r--r--lib/pleroma/plugs/http_signature.ex49
-rw-r--r--lib/pleroma/plugs/mapped_signature_to_identity_plug.ex70
-rw-r--r--lib/pleroma/signature.ex17
-rw-r--r--lib/pleroma/user.ex22
-rw-r--r--lib/pleroma/user_invite_token.ex2
-rw-r--r--lib/pleroma/web/activity_pub/mrf.ex10
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex55
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex60
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex13
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex12
-rw-r--r--lib/pleroma/web/admin_api/config.ex14
-rw-r--r--lib/pleroma/web/common_api/utils.ex7
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex12
-rw-r--r--lib/pleroma/web/rich_media/parser.ex45
-rw-r--r--lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex52
-rw-r--r--lib/pleroma/web/rich_media/parsers/ttl/ttl.ex3
-rw-r--r--lib/pleroma/web/router.ex26
-rw-r--r--lib/pleroma/web/streamer.ex40
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex32
20 files changed, 439 insertions, 120 deletions
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 8a78b4fe6..c9b84b8f9 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -62,6 +62,10 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user unsubscribe NICKNAME
+ ## Unsubscribe local users from an entire instance and deactivate all accounts
+
+ mix pleroma.user unsubscribe_all_from_instance INSTANCE
+
## Create a password reset link.
mix pleroma.user reset_password NICKNAME
@@ -246,6 +250,20 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
+ def run(["unsubscribe_all_from_instance", instance]) do
+ start_pleroma()
+
+ Pleroma.User.Query.build(%{nickname: "@#{instance}"})
+ |> Pleroma.RepoStreamer.chunk_stream(500)
+ |> Stream.each(fn users ->
+ users
+ |> Enum.each(fn user ->
+ run(["unsubscribe", user.nickname])
+ end)
+ end)
+ |> Stream.run()
+ end
+
def run(["set", nickname | rest]) do
start_pleroma()
diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex
index e2874c469..d87fa52fa 100644
--- a/lib/pleroma/plugs/http_signature.ex
+++ b/lib/pleroma/plugs/http_signature.ex
@@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
- alias Pleroma.Web.ActivityPub.Utils
import Plug.Conn
require Logger
@@ -16,38 +15,30 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
end
def call(conn, _opts) do
- user = Utils.get_ap_id(conn.params["actor"])
- Logger.debug("Checking sig for #{user}")
[signature | _] = get_req_header(conn, "signature")
- cond do
- signature && String.contains?(signature, user) ->
- # set (request-target) header to the appropriate value
- # we also replace the digest header with the one we computed
- conn =
- conn
- |> put_req_header(
- "(request-target)",
- String.downcase("#{conn.method}") <> " #{conn.request_path}"
- )
-
- conn =
- if conn.assigns[:digest] do
- conn
- |> put_req_header("digest", conn.assigns[:digest])
- else
- conn
- end
-
- assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
+ if signature do
+ # set (request-target) header to the appropriate value
+ # we also replace the digest header with the one we computed
+ conn =
+ conn
+ |> put_req_header(
+ "(request-target)",
+ String.downcase("#{conn.method}") <> " #{conn.request_path}"
+ )
- signature ->
- Logger.debug("Signature not from actor")
- assign(conn, :valid_signature, false)
+ conn =
+ if conn.assigns[:digest] do
+ conn
+ |> put_req_header("digest", conn.assigns[:digest])
+ else
+ conn
+ end
- true ->
- Logger.debug("No signature header!")
- conn
+ assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
+ else
+ Logger.debug("No signature header!")
+ conn
end
end
end
diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
new file mode 100644
index 000000000..ce8494b9d
--- /dev/null
+++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex
@@ -0,0 +1,70 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 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) do
+ Signature.key_id_to_actor_id(key_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=#{key_id_from_conn(conn)}, actor=#{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=#{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=#{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/signature.ex b/lib/pleroma/signature.ex
index a45c70a9d..0bf49fd7c 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -9,10 +9,19 @@ defmodule Pleroma.Signature do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
- defp key_id_to_actor_id(key_id) do
- URI.parse(key_id)
- |> Map.put(:fragment, nil)
- |> URI.to_string()
+ def key_id_to_actor_id(key_id) do
+ uri =
+ URI.parse(key_id)
+ |> Map.put(:fragment, nil)
+
+ uri =
+ if String.ends_with?(uri.path, "/publickey") do
+ Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
+ else
+ uri
+ end
+
+ URI.to_string(uri)
end
def fetch_public_key(conn) do
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index c91fbb68a..982ca8bc1 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -586,12 +586,23 @@ defmodule Pleroma.User do
@spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil)
+ @spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do
q = get_followers_query(user, page)
{:ok, Repo.all(q)}
end
+ @spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
+ def get_external_followers(user, page \\ nil) do
+ q =
+ user
+ |> get_followers_query(page)
+ |> User.Query.build(%{external: true})
+
+ {:ok, Repo.all(q)}
+ end
+
def get_followers_ids(user, page \\ nil) do
q = get_followers_query(user, page)
@@ -873,12 +884,17 @@ defmodule Pleroma.User do
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
blocks = info.blocks
- domain_blocks = info.domain_blocks
+
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks)
+
%{host: host} = URI.parse(ap_id)
- Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host))
+ Enum.member?(blocks, ap_id) ||
+ Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
end
+ def blocks?(nil, _), do: false
+
def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.info.subscribers, user.ap_id)
@@ -1211,7 +1227,7 @@ defmodule Pleroma.User do
data
|> Map.put(:name, blank?(data[:name]) || data[:nickname])
|> remote_user_creation()
- |> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname)
+ |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
|> set_cache()
end
diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex
index fadc89891..b9e80acdd 100644
--- a/lib/pleroma/user_invite_token.ex
+++ b/lib/pleroma/user_invite_token.ex
@@ -74,7 +74,7 @@ defmodule Pleroma.UserInviteToken do
@spec find_by_token(token()) :: {:ok, UserInviteToken.t()} | nil
def find_by_token(token) do
- with invite <- Repo.get_by(UserInviteToken, token: token) do
+ with %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, token: token) do
{:ok, invite}
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex
index 10ceef715..dd204b21c 100644
--- a/lib/pleroma/web/activity_pub/mrf.ex
+++ b/lib/pleroma/web/activity_pub/mrf.ex
@@ -25,4 +25,14 @@ defmodule Pleroma.Web.ActivityPub.MRF do
defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: []
+
+ @spec subdomains_regex([String.t()]) :: [Regex.t()]
+ def subdomains_regex(domains) when is_list(domains) do
+ for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
+ end
+
+ @spec subdomain_match?([Regex.t()], String.t()) :: boolean()
+ def subdomain_match?(domains, host) do
+ Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
+ end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 433d23c5f..2cf63d3db 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -4,22 +4,29 @@
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance"
- @behaviour Pleroma.Web.ActivityPub.MRF
+ @behaviour MRF
defp check_accept(%{host: actor_host} = _actor_info, object) do
- accepts = Pleroma.Config.get([:mrf_simple, :accept])
+ accepts =
+ Pleroma.Config.get([:mrf_simple, :accept])
+ |> MRF.subdomains_regex()
cond do
accepts == [] -> {:ok, object}
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
- Enum.member?(accepts, actor_host) -> {:ok, object}
+ MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil}
end
end
defp check_reject(%{host: actor_host} = _actor_info, object) do
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do
+ rejects =
+ Pleroma.Config.get([:mrf_simple, :reject])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil}
else
{:ok, object}
@@ -31,8 +38,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
)
when length(child_attachment) > 0 do
+ media_removal =
+ Pleroma.Config.get([:mrf_simple, :media_removal])
+ |> MRF.subdomains_regex()
+
object =
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do
+ if MRF.subdomain_match?(media_removal, actor_host) do
child_object = Map.delete(object["object"], "attachment")
Map.put(object, "object", child_object)
else
@@ -51,8 +62,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
"object" => child_object
} = object
) do
+ media_nsfw =
+ Pleroma.Config.get([:mrf_simple, :media_nsfw])
+ |> MRF.subdomains_regex()
+
object =
- if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do
+ if MRF.subdomain_match?(media_nsfw, actor_host) do
tags = (child_object["tag"] || []) ++ ["nsfw"]
child_object = Map.put(child_object, "tag", tags)
child_object = Map.put(child_object, "sensitive", true)
@@ -67,12 +82,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
+ timeline_removal =
+ Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
+ |> MRF.subdomains_regex()
+
object =
- with true <-
- Enum.member?(
- Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
- actor_host
- ),
+ with true <- MRF.subdomain_match?(timeline_removal, actor_host),
user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
to =
@@ -94,7 +109,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do
+ report_removal =
+ Pleroma.Config.get([:mrf_simple, :report_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil}
else
{:ok, object}
@@ -104,7 +123,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do
+ avatar_removal =
+ Pleroma.Config.get([:mrf_simple, :avatar_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(avatar_removal, actor_host) do
{:ok, Map.delete(object, "icon")}
else
{:ok, object}
@@ -114,7 +137,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
- if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do
+ banner_removal =
+ Pleroma.Config.get([:mrf_simple, :banner_removal])
+ |> MRF.subdomains_regex()
+
+ if MRF.subdomain_match?(banner_removal, actor_host) do
{:ok, Map.delete(object, "image")}
else
{:ok, object}
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index c505223f7..016d78216 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -87,18 +87,23 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
if public do
true
else
- inbox_info = URI.parse(inbox)
- !Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
+ %{host: host} = URI.parse(inbox)
+
+ quarantined_instances =
+ Config.get([:instance, :quarantined_instances], [])
+ |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
+
+ !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end
end
+ @spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do
- followers =
+ {:ok, followers} =
if actor.follower_address in activity.recipients do
- {:ok, followers} = User.get_followers(actor)
- Enum.filter(followers, &(!&1.local))
+ User.get_external_followers(actor)
else
- []
+ {:ok, []}
end
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
@@ -112,6 +117,45 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|> Enum.map(& &1.ap_id)
end
+ @as_public "https://www.w3.org/ns/activitystreams#Public"
+
+ defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
+ do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
+
+ @doc """
+ Determine a user inbox to use based on heuristics. These heuristics
+ are based on an approximation of the ``sharedInbox`` rules in the
+ [ActivityPub specification][ap-sharedinbox].
+
+ Please do not edit this function (or its children) without reading
+ the spec, as editing the code is likely to introduce some breakage
+ without some familiarity.
+
+ [ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
+ """
+ def determine_inbox(
+ %Activity{data: activity_data},
+ %User{info: %{source_data: data}} = user
+ ) do
+ to = activity_data["to"] || []
+ cc = activity_data["cc"] || []
+ type = activity_data["type"]
+
+ cond do
+ type == "Delete" ->
+ maybe_use_sharedinbox(user)
+
+ @as_public in to || @as_public in cc ->
+ maybe_use_sharedinbox(user)
+
+ length(to) + length(cc) > 1 ->
+ maybe_use_sharedinbox(user)
+
+ true ->
+ data["inbox"]
+ end
+ end
+
@doc """
Publishes an activity with BCC to all relevant peers.
"""
@@ -166,8 +210,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
- |> Enum.map(fn %{info: %{source_data: data}} ->
- (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
+ |> Enum.map(fn %User{} = user ->
+ determine_inbox(activity, user)
end)
|> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index 2666edc7c..097fceb08 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Repo
alias Pleroma.User
+ @public "https://www.w3.org/ns/activitystreams#Public"
+
+ @spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
-
- def is_public?(data) do
- "https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
- end
+ def is_public?(data), do: @public in (data["to"] ++ (data["cc"] || []))
def is_private?(activity) do
with false <- is_public?(activity),
@@ -69,15 +69,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
def get_visibility(object) do
- public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
- public in to ->
+ @public in to ->
"public"
- public in cc ->
+ @public in cc ->
"unlisted"
# this should use the sql for the object's activity
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index 5c64bb81b..1ae5acd91 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -291,11 +291,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@doc "Revokes invite by token"
def revoke_invite(conn, %{"token" => token}) do
- invite = UserInviteToken.find_by_token!(token)
- {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true})
-
- conn
- |> json(AccountView.render("invite.json", %{invite: updated_invite}))
+ with {:ok, invite} <- UserInviteToken.find_by_token(token),
+ {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
+ conn
+ |> json(AccountView.render("invite.json", %{invite: updated_invite}))
+ else
+ nil -> {:error, :not_found}
+ end
end
@doc "Get a password reset token (base64 string) for given nickname"
diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex
index b4eb8e002..dde05ea7b 100644
--- a/lib/pleroma/web/admin_api/config.ex
+++ b/lib/pleroma/web/admin_api/config.ex
@@ -84,6 +84,7 @@ defmodule Pleroma.Web.AdminAPI.Config do
end
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
+ defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
defp do_convert(entity) when is_tuple(entity),
do: %{"tuple" => do_convert(Tuple.to_list(entity))}
@@ -113,11 +114,15 @@ defmodule Pleroma.Web.AdminAPI.Config do
defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
- cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
- {dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
+ {dispatch_settings, []} = do_eval(entity)
{:dispatch, [dispatch_settings]}
end
+ defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
+ {partial_chain, []} = do_eval(entity)
+ {:partial_chain, partial_chain}
+ end
+
defp do_transform(%{"tuple" => entity}) do
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end
@@ -149,4 +154,9 @@ defmodule Pleroma.Web.AdminAPI.Config do
do: String.to_existing_atom("Elixir." <> value),
else: value
end
+
+ defp do_eval(entity) do
+ cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
+ Code.eval_string(cleaned_string, [], requires: [], macros: [])
+ end
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index fcc000969..94462c3dd 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -439,6 +439,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
+ # Do not notify subscribers if author is making a reply
+ def maybe_notify_subscribers(recipients, %Activity{
+ object: %Object{data: %{"inReplyTo" => _ap_id}}
+ }) do
+ recipients
+ end
+
def maybe_notify_subscribers(
recipients,
%Activity{data: %{"actor" => actor, "type" => type}} = activity
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 877430a1d..d660f3f05 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -440,7 +440,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_id(params["id"]) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do
params =
params
|> Map.put("tag", params["tagged"])
@@ -883,7 +883,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^likes)
- users = Repo.all(q)
+
+ users =
+ Repo.all(q)
+ |> Enum.filter(&(not User.blocks?(user, &1)))
conn
|> put_view(AccountView)
@@ -897,7 +900,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^announces)
- users = Repo.all(q)
+
+ users =
+ Repo.all(q)
+ |> Enum.filter(&(not User.blocks?(user, &1)))
conn
|> put_view(AccountView)
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 0d2523338..185156375 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -24,6 +24,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
Cachex.fetch!(:rich_media_cache, url, fn _ ->
{:commit, parse_url(url)}
end)
+ |> set_ttl_based_on_image(url)
rescue
e ->
{:error, "Cachex error: #{inspect(e)}"}
@@ -31,6 +32,50 @@ defmodule Pleroma.Web.RichMedia.Parser do
end
end
+ @doc """
+ Set the rich media cache based on the expiration time of image.
+
+ Adopt behaviour `Pleroma.Web.RichMedia.Parser.TTL`
+
+ ## Example
+
+ defmodule MyModule do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+ def ttl(data, url) do
+ image_url = Map.get(data, :image)
+ # do some parsing in the url and get the ttl of the image
+ # and return ttl is unix time
+ parse_ttl_from_url(image_url)
+ end
+ end
+
+ Define the module in the config
+
+ config :pleroma, :rich_media,
+ ttl_setters: [MyModule]
+ """
+ def set_ttl_based_on_image({:ok, data}, url) do
+ with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url),
+ ttl when is_number(ttl) <- get_ttl_from_image(data, url) do
+ Cachex.expire_at(:rich_media_cache, url, ttl * 1000)
+ {:ok, data}
+ else
+ _ ->
+ {:ok, data}
+ end
+ end
+
+ defp get_ttl_from_image(data, url) do
+ Pleroma.Config.get([:rich_media, :ttl_setters])
+ |> Enum.reduce({:ok, nil}, fn
+ module, {:ok, _ttl} ->
+ module.ttl(data, url)
+
+ _, error ->
+ error
+ end)
+ end
+
defp parse_url(url) do
try do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
new file mode 100644
index 000000000..014c0935f
--- /dev/null
+++ b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
@@ -0,0 +1,52 @@
+defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+
+ @impl Pleroma.Web.RichMedia.Parser.TTL
+ def ttl(data, _url) do
+ image = Map.get(data, :image)
+
+ if is_aws_signed_url(image) do
+ image
+ |> parse_query_params()
+ |> format_query_params()
+ |> get_expiration_timestamp()
+ end
+ end
+
+ defp is_aws_signed_url(""), do: nil
+ defp is_aws_signed_url(nil), do: nil
+
+ defp is_aws_signed_url(image) when is_binary(image) do
+ %URI{host: host, query: query} = URI.parse(image)
+
+ if String.contains?(host, "amazonaws.com") and
+ String.contains?(query, "X-Amz-Expires") do
+ image
+ else
+ nil
+ end
+ end
+
+ defp is_aws_signed_url(_), do: nil
+
+ defp parse_query_params(image) do
+ %URI{query: query} = URI.parse(image)
+ query
+ end
+
+ defp format_query_params(query) do
+ query
+ |> String.split(~r/&|=/)
+ |> Enum.chunk_every(2)
+ |> Map.new(fn [k, v] -> {k, v} end)
+ end
+
+ defp get_expiration_timestamp(params) when is_map(params) do
+ {:ok, date} =
+ params
+ |> Map.get("X-Amz-Date")
+ |> Timex.parse("{ISO:Basic:Z}")
+
+ Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))
+ end
+end
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
new file mode 100644
index 000000000..6b3ec6d30
--- /dev/null
+++ b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
@@ -0,0 +1,3 @@
+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 3d9249601..a9f3826fc 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -154,22 +154,12 @@ defmodule Pleroma.Web.Router do
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
- # TODO: to be removed at version 1.0
- delete("/user", AdminAPIController, :user_delete)
- post("/user", AdminAPIController, :user_create)
-
delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :user_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
- # TODO: to be removed at version 1.0
- get("/permission_group/:nickname", AdminAPIController, :right_get)
- get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
- post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
- delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
-
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
@@ -190,9 +180,6 @@ defmodule Pleroma.Web.Router do
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
post("/users/email_invite", AdminAPIController, :email_invite)
- # TODO: to be removed at version 1.0
- get("/password_reset", AdminAPIController, :get_password_reset)
-
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
get("/users", AdminAPIController, :list_users)
@@ -618,6 +605,7 @@ defmodule Pleroma.Web.Router do
pipeline :activitypub do
plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
+ plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
end
scope "/", Pleroma.Web.ActivityPub do
@@ -663,6 +651,12 @@ defmodule Pleroma.Web.Router do
end
end
+ scope "/", Pleroma.Web.ActivityPub do
+ pipe_through(:activitypub)
+ post("/inbox", ActivityPubController, :inbox)
+ post("/users/:nickname/inbox", ActivityPubController, :inbox)
+ end
+
scope "/relay", Pleroma.Web.ActivityPub do
pipe_through(:ap_service_actor)
@@ -677,12 +671,6 @@ defmodule Pleroma.Web.Router do
post("/inbox", ActivityPubController, :inbox)
end
- scope "/", Pleroma.Web.ActivityPub do
- pipe_through(:activitypub)
- post("/inbox", ActivityPubController, :inbox)
- post("/users/:nickname/inbox", ActivityPubController, :inbox)
- end
-
scope "/.well-known", Pleroma.Web do
pipe_through(:well_known)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index 4f325113a..86e2dc4dd 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.Streamer do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.NotificationView
@keepalive_interval :timer.seconds(30)
@@ -118,10 +119,14 @@ defmodule Pleroma.Web.Streamer do
topics
|> Map.get("#{topic}:#{item.user_id}", [])
|> Enum.each(fn socket ->
- send(
- socket.transport_pid,
- {:text, represent_notification(socket.assigns[:user], item)}
- )
+ with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id),
+ true <- should_send?(user, item),
+ false <- CommonAPI.thread_muted?(user, item.activity) do
+ send(
+ socket.transport_pid,
+ {:text, represent_notification(socket.assigns[:user], item)}
+ )
+ end
end)
{:noreply, topics}
@@ -225,19 +230,32 @@ defmodule Pleroma.Web.Streamer do
|> Jason.encode!()
end
+ defp should_send?(%User{} = user, %Activity{} = item) do
+ blocks = user.info.blocks || []
+ mutes = user.info.mutes || []
+ reblog_mutes = user.info.muted_reblogs || []
+
+ with parent when not is_nil(parent) <- Object.normalize(item),
+ true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
+ true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
+ true <- thread_containment(item, user) do
+ true
+ else
+ _ -> false
+ end
+ end
+
+ defp should_send?(%User{} = user, %Notification{activity: activity}) do
+ should_send?(user, activity)
+ end
+
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
Enum.each(topics[topic] || [], fn socket ->
# Get the current user so we have up-to-date blocks etc.
if socket.assigns[:user] do
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
- blocks = user.info.blocks || []
- mutes = user.info.mutes || []
- reblog_mutes = user.info.muted_reblogs || []
- with parent when not is_nil(parent) <- Object.normalize(item),
- true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
- true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
- true <- thread_containment(item, user) do
+ if should_send?(user, item) do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
else
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index c10c66ff2..9e4da7dca 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -8,7 +8,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
require Logger
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.Emoji
+ alias Pleroma.Healthcheck
alias Pleroma.Notification
alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User
@@ -23,7 +25,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
- with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do
+ with %User{} = user <- User.get_cached_by_nickname(nick),
+ avatar = User.avatar_url(user) do
conn
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
else
@@ -338,20 +341,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def healthcheck(conn, _params) do
- info =
- if Pleroma.Config.get([:instance, :healthcheck]) do
- Pleroma.Healthcheck.system_info()
- else
- %{}
- end
+ with true <- Config.get([:instance, :healthcheck]),
+ %{healthy: true} = info <- Healthcheck.system_info() do
+ json(conn, info)
+ else
+ %{healthy: false} = info ->
+ service_unavailable(conn, info)
- conn =
- if info[:healthy] do
- conn
- else
- Plug.Conn.put_status(conn, :service_unavailable)
- end
+ _ ->
+ service_unavailable(conn, %{})
+ end
+ end
- json(conn, info)
+ defp service_unavailable(conn, info) do
+ conn
+ |> put_status(:service_unavailable)
+ |> json(info)
end
end