Merge remote-tracking branch 'origin/develop' into paradedb-search-backend

This commit is contained in:
Mark Felder 2026-03-25 15:09:19 -07:00
commit 2cc7b8356c
118 changed files with 1639 additions and 851 deletions

View file

@ -0,0 +1 @@
Various bookmark folders-related improvements

View file

@ -0,0 +1 @@
Allow fine-grained announce visibilities

View file

@ -0,0 +1 @@
No-op code correctness improvements detected by Elixir 1.19 compiler

View file

@ -0,0 +1 @@
Support lists `exclusive` param

View file

@ -0,0 +1 @@
Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled

View file

@ -0,0 +1 @@
Fix fetching Hubzilla Actors with alsoKnownAs as string

View file

@ -0,0 +1 @@
Fix /phoenix/live_dashboard redirect not working when user added a path segment

View file

@ -0,0 +1 @@
Fix 404 error codes for missing static files

View file

@ -0,0 +1 @@
Correct old migrations for expiring activities and user access tokens.

View file

@ -0,0 +1 @@
Stop the rate limiter from crashing when run with wrong settings.

View file

@ -0,0 +1 @@
Restore embeds route

View file

@ -0,0 +1 @@
ReverseProxy: Recursively follow redirects until redirect_limit is reached

View file

@ -0,0 +1 @@
Filter indexable activities before inserting indexing jobs into the queue.

View file

View file

@ -0,0 +1 @@
Avoid code duplication in UserView

View file

@ -74,7 +74,7 @@ Pleroma does not process remote images and therefore cannot include fields such
The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID. The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID.
The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. Bookmarking an already bookmarked post will update the folder association, or remove it if `folder_id` is omitted or `null`.
## Accounts ## Accounts

View file

@ -19,7 +19,7 @@ defmodule Pleroma.Bookmark do
schema "bookmarks" do schema "bookmarks" do
belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType)
belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType) belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.Type)
timestamps() timestamps()
end end
@ -38,7 +38,7 @@ defmodule Pleroma.Bookmark do
|> validate_required([:user_id, :activity_id]) |> validate_required([:user_id, :activity_id])
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|> Repo.insert( |> Repo.insert(
on_conflict: [set: [folder_id: folder_id]], on_conflict: [set: [folder_id: folder_id, updated_at: NaiveDateTime.utc_now()]],
conflict_target: [:user_id, :activity_id] conflict_target: [:user_id, :activity_id]
) )
end end
@ -76,11 +76,4 @@ defmodule Pleroma.Bookmark do
|> Repo.one() |> Repo.one()
|> Repo.delete() |> Repo.delete()
end end
def set_folder(bookmark, folder_id) do
bookmark
|> cast(%{folder_id: folder_id}, [:folder_id])
|> validate_required([:folder_id])
|> Repo.update()
end
end end

View file

@ -14,7 +14,7 @@ defmodule Pleroma.BookmarkFolder do
alias Pleroma.User alias Pleroma.User
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} @primary_key {:id, FlakeId.Ecto.Type, autogenerate: true}
schema "bookmark_folders" do schema "bookmark_folders" do
field(:name, :string) field(:name, :string)

View file

@ -10,6 +10,7 @@ defmodule Pleroma.ConfigDB do
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
alias __MODULE__ alias __MODULE__
alias Pleroma.EctoType.Config.RateLimit
alias Pleroma.Repo alias Pleroma.Repo
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@ -60,8 +61,59 @@ defmodule Pleroma.ConfigDB do
|> cast(params, [:key, :group, :value]) |> cast(params, [:key, :group, :value])
|> validate_required([:key, :group, :value]) |> validate_required([:key, :group, :value])
|> unique_constraint(:key, name: :config_group_key_index) |> unique_constraint(:key, name: :config_group_key_index)
|> validate_rate_limit()
end end
defp validate_rate_limit(changeset) do
group = get_field(changeset, :group)
key = get_field(changeset, :key)
if group == :pleroma and key == :rate_limit do
value = get_field(changeset, :value)
case normalize_rate_limit(value) do
{:ok, normalized_value} ->
put_change(changeset, :value, normalized_value)
{:error, {limiter_name, reason}} ->
add_error(
changeset,
:value,
"invalid :rate_limit value for #{inspect(limiter_name)}: #{reason}"
)
end
else
changeset
end
end
defp normalize_rate_limit(nil), do: {:ok, nil}
defp normalize_rate_limit(%{} = value), do: normalize_rate_limit(Map.to_list(value))
defp normalize_rate_limit(value) when is_list(value) do
if Keyword.keyword?(value) do
value
|> Enum.reduce_while({:ok, []}, fn {limiter_name, limiter_value}, {:ok, acc} ->
case RateLimit.cast_with_error(limiter_value) do
{:ok, normalized_limiter_value} ->
{:cont, {:ok, [{limiter_name, normalized_limiter_value} | acc]}}
{:error, reason} ->
{:halt, {:error, {limiter_name, reason}}}
end
end)
|> case do
{:ok, acc} -> {:ok, Enum.reverse(acc)}
{:error, _} = error -> error
end
else
{:error, {:rate_limit, "must be a keyword list"}}
end
end
defp normalize_rate_limit(_), do: {:error, {:rate_limit, "must be a keyword list"}}
defp create(params) do defp create(params) do
%ConfigDB{} %ConfigDB{}
|> changeset(params) |> changeset(params)

View file

@ -0,0 +1,71 @@
# Pleroma: A lightweight social networking server
# Copyright © Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EctoType.Config.RateLimit do
@moduledoc false
use Ecto.Type
@type t ::
nil
| {non_neg_integer(), non_neg_integer()}
| [{non_neg_integer(), non_neg_integer()}]
@impl true
def type, do: :term
@impl true
def cast(value) do
case cast_with_error(value) do
{:ok, normalized} -> {:ok, normalized}
{:error, _reason} -> :error
end
end
@impl true
def load(value), do: cast(value)
@impl true
def dump(value), do: cast(value)
@spec cast_with_error(term()) :: {:ok, t()} | {:error, String.t()}
def cast_with_error(nil), do: {:ok, nil}
def cast_with_error({scale, limit}) do
with {:ok, scale} <- parse_integer(scale, "scale"),
{:ok, limit} <- parse_integer(limit, "limit"),
true <- scale >= 1 and limit >= 1 do
{:ok, {scale, limit}}
else
false -> {:error, "scale and limit must be >= 1"}
{:error, reason} -> {:error, reason}
end
end
def cast_with_error([{_, _} = unauth, {_, _} = auth]) do
with {:ok, unauth} <- cast_with_error(unauth),
{:ok, auth} <- cast_with_error(auth) do
{:ok, [unauth, auth]}
else
{:error, reason} -> {:error, reason}
end
end
def cast_with_error(_),
do:
{:error, "must be a {scale, limit} tuple, a [{scale, limit}, {scale, limit}] list, or nil"}
defp parse_integer(value, _label) when is_integer(value), do: {:ok, value}
defp parse_integer(value, label) when is_binary(value) do
value = String.trim(value)
case Integer.parse(value) do
{number, ""} -> {:ok, number}
_ -> {:error, "#{label} must be an integer"}
end
end
defp parse_integer(_value, label), do: {:error, "#{label} must be an integer"}
end

View file

@ -21,10 +21,13 @@ defmodule Pleroma.Gopher.Server do
def init([ip, port]) do def init([ip, port]) do
Logger.info("Starting gopher server on #{port}") Logger.info("Starting gopher server on #{port}")
Process.flag(:trap_exit, true)
listener = :gopher
{:ok, _pid} = {:ok, _pid} =
:ranch.start_listener( :ranch.start_listener(
:gopher, listener,
:ranch_tcp, :ranch_tcp,
%{ %{
num_acceptors: 100, num_acceptors: 100,
@ -35,7 +38,11 @@ defmodule Pleroma.Gopher.Server do
[] []
) )
{:ok, %{ip: ip, port: port}} {:ok, %{ip: ip, port: port, listener: listener}}
end
def terminate(_reason, state) do
:ranch.stop_listener(state.listener)
end end
end end

View file

@ -17,13 +17,14 @@ defmodule Pleroma.List do
field(:title, :string) field(:title, :string)
field(:following, {:array, :string}, default: []) field(:following, {:array, :string}, default: [])
field(:ap_id, :string) field(:ap_id, :string)
field(:exclusive, :boolean, default: false)
timestamps() timestamps()
end end
def title_changeset(list, attrs \\ %{}) do def update_changeset(list, attrs \\ %{}) do
list list
|> cast(attrs, [:title]) |> cast(attrs, [:title, :exclusive])
|> validate_required([:title]) |> validate_required([:title])
end end
@ -91,14 +92,14 @@ defmodule Pleroma.List do
|> Repo.all() |> Repo.all()
end end
def rename(%Pleroma.List{} = list, title) do def update(%Pleroma.List{} = list, params) do
list list
|> title_changeset(%{title: title}) |> update_changeset(params)
|> Repo.update() |> Repo.update()
end end
def create(title, %User{} = creator) do def create(params, %User{} = creator) do
changeset = title_changeset(%Pleroma.List{user_id: creator.id}, %{title: title}) changeset = update_changeset(%Pleroma.List{user_id: creator.id}, params)
if changeset.valid? do if changeset.valid? do
Repo.transaction(fn -> Repo.transaction(fn ->
@ -149,4 +150,14 @@ defmodule Pleroma.List do
end end
def member?(_, _), do: false def member?(_, _), do: false
def get_exclusive_list_members(%User{id: user_id}) do
Pleroma.List
|> where([l], l.user_id == ^user_id)
|> where([l], l.exclusive == true)
|> select([l], l.following)
|> Repo.all()
|> List.flatten()
|> Enum.uniq()
end
end end

View file

@ -78,7 +78,7 @@ defmodule Pleroma.Marker do
defp get_marker(user, timeline) do defp get_marker(user, timeline) do
case Repo.find_resource(get_query(user, timeline)) do case Repo.find_resource(get_query(user, timeline)) do
{:ok, marker} -> %__MODULE__{marker | user: user} {:ok, %__MODULE__{} = marker} -> %{marker | user: user}
_ -> %__MODULE__{timeline: timeline, user_id: user.id} _ -> %__MODULE__{timeline: timeline, user_id: user.id}
end end
end end

View file

@ -8,6 +8,7 @@ defmodule Pleroma.MFA.Changeset do
alias Pleroma.User alias Pleroma.User
def disable(%Ecto.Changeset{} = changeset, force \\ false) do def disable(%Ecto.Changeset{} = changeset, force \\ false) do
%Settings{} =
settings = settings =
changeset changeset
|> Ecto.Changeset.apply_changes() |> Ecto.Changeset.apply_changes()
@ -20,20 +21,20 @@ defmodule Pleroma.MFA.Changeset do
end end
end end
def disable_totp(%User{multi_factor_authentication_settings: settings} = user) do def disable_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do
user user
|> put_change(%Settings{settings | totp: %Settings.TOTP{}}) |> put_change(%Settings{settings | totp: %Settings.TOTP{}})
end end
def confirm_totp(%User{multi_factor_authentication_settings: settings} = user) do def confirm_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do
totp_settings = %Settings.TOTP{settings.totp | confirmed: true} totp_settings = %Settings.TOTP{(%Settings.TOTP{} = settings.totp) | confirmed: true}
user user
|> put_change(%Settings{settings | totp: totp_settings, enabled: true}) |> put_change(%Settings{settings | totp: totp_settings, enabled: true})
end end
def setup_totp(%User{} = user, attrs) do def setup_totp(%User{} = user, attrs) do
mfa_settings = MFA.fetch_settings(user) %Settings{} = mfa_settings = MFA.fetch_settings(user)
totp_settings = totp_settings =
%Settings.TOTP{} %Settings.TOTP{}
@ -46,7 +47,7 @@ defmodule Pleroma.MFA.Changeset do
def cast_backup_codes(%User{} = user, codes) do def cast_backup_codes(%User{} = user, codes) do
user user
|> put_change(%Settings{ |> put_change(%Settings{
user.multi_factor_authentication_settings (%Settings{} = user.multi_factor_authentication_settings)
| backup_codes: codes | backup_codes: codes
}) })
end end

View file

@ -4,6 +4,9 @@
defmodule Pleroma.ReverseProxy.Client.Hackney do defmodule Pleroma.ReverseProxy.Client.Hackney do
@behaviour Pleroma.ReverseProxy.Client @behaviour Pleroma.ReverseProxy.Client
@redirect_limit 5
require Logger
# In-app redirect handler to avoid Hackney redirect bugs: # In-app redirect handler to avoid Hackney redirect bugs:
# - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney) # - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney)
@ -31,26 +34,15 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
if opts[:follow_redirect] != false do if opts[:follow_redirect] != false do
{_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end) {_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end)
env = %{method: method, headers: headers, body: body, req_opts: req_opts}
res = :hackney.request(method, url, headers, body, req_opts) res = :hackney.request(method, url, headers, body, req_opts)
case res do case res do
{:ok, code, resp_headers, _client} when code in @redirect_statuses -> {:ok, code, resp_headers, _client} when code in @redirect_statuses ->
:hackney.request( redirect(url, resp_headers, env, @redirect_limit)
method,
absolute_redirect_url(url, resp_headers),
headers,
body,
req_opts
)
{:ok, code, resp_headers} when code in @redirect_statuses -> {:ok, code, resp_headers} when code in @redirect_statuses ->
:hackney.request( redirect(url, resp_headers, env, @redirect_limit)
method,
absolute_redirect_url(url, resp_headers),
headers,
body,
req_opts
)
_ -> _ ->
res res
@ -71,4 +63,32 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do
@impl true @impl true
def close(ref), do: :hackney.close(ref) def close(ref), do: :hackney.close(ref)
defp redirect(url, resp_headers, env, limit) when limit == 0 do
new_url = absolute_redirect_url(url, resp_headers)
Logger.debug(
"#{__MODULE__}: Handling redirect #{url} -> #{new_url}; redirect limit was reached - returning response after final redirect"
)
:hackney.request(env.method, new_url, env.headers, env.body, env.req_opts)
end
defp redirect(url, resp_headers, env, limit) do
new_url = absolute_redirect_url(url, resp_headers)
Logger.debug("#{__MODULE__}: handling redirect #{url} -> #{new_url}; limit = #{limit}")
res = :hackney.request(env.method, new_url, env.headers, env.body, env.req_opts)
case res do
{:ok, code, new_resp_headers, _client} when code in @redirect_statuses ->
redirect(new_url, new_resp_headers, env, limit - 1)
{:ok, code, new_resp_headers} when code in @redirect_statuses ->
redirect(new_url, new_resp_headers, env, limit - 1)
_ ->
res
end
end
end end

View file

@ -1,11 +1,28 @@
defmodule Pleroma.Search do defmodule Pleroma.Search do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Workers.SearchIndexingWorker alias Pleroma.Workers.SearchIndexingWorker
def add_to_index(%Pleroma.Activity{id: activity_id}) do @spec add_to_index(Activity.t()) :: {:ok, Oban.Job.t() | :noop} | {:error, Oban.Job.changeset()}
def add_to_index(%Activity{id: activity_id, object: %Object{} = object} = activity) do
with {_, true} <- {:indexable, indexable?(activity)},
{_, "public"} <- {:visibility, Visibility.get_visibility(object)} do
SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id}) SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id})
|> Oban.insert() |> Oban.insert()
else
_ -> {:ok, :noop}
end
end end
def add_to_index(%Activity{id: activity_id}) do
case Activity.get_by_id_with_object(activity_id) do
%Activity{} = preloaded -> add_to_index(preloaded)
_ -> :ok
end
end
@spec remove_from_index(Object.t()) :: {:ok, Oban.Job.t()} | {:error, Oban.Job.changeset()}
def remove_from_index(%Pleroma.Object{id: object_id}) do def remove_from_index(%Pleroma.Object{id: object_id}) do
SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id}) SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id})
|> Oban.insert() |> Oban.insert()
@ -20,4 +37,7 @@ defmodule Pleroma.Search do
search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module = Pleroma.Config.get([Pleroma.Search, :module])
search_module.healthcheck_endpoints() search_module.healthcheck_endpoints()
end end
defp indexable?(%Activity{data: %{"type" => "Create"}}), do: true
defp indexable?(_), do: false
end end

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Search.Meilisearch do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config.Getting, as: Config alias Pleroma.Config.Getting, as: Config
alias Pleroma.Object
import Pleroma.Search.DatabaseSearch import Pleroma.Search.DatabaseSearch
import Ecto.Query import Ecto.Query
@ -156,14 +157,13 @@ defmodule Pleroma.Search.Meilisearch do
end end
@impl true @impl true
def add_to_index(activity) do def add_to_index(%Activity{object: %Object{} = object} = activity) do
maybe_search_data = object_to_search_data(activity.object) search_data = object_to_search_data(object)
if activity.data["type"] == "Create" and maybe_search_data do
result = result =
meili_put( meili_put(
"/indexes/objects/documents", "/indexes/objects/documents",
[maybe_search_data] [search_data]
) )
with {:ok, %{"status" => "enqueued"}} <- result do with {:ok, %{"status" => "enqueued"}} <- result do
@ -175,10 +175,6 @@ defmodule Pleroma.Search.Meilisearch do
Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}")
{:error, result} {:error, result}
end end
else
# The post isn't something we can search, that's ok
:ok
end
end end
@impl true @impl true

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Search.QdrantSearch do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config.Getting, as: Config alias Pleroma.Config.Getting, as: Config
alias Pleroma.Object
alias __MODULE__.OpenAIClient alias __MODULE__.OpenAIClient
alias __MODULE__.QdrantClient alias __MODULE__.QdrantClient
@ -82,12 +83,10 @@ defmodule Pleroma.Search.QdrantSearch do
end end
@impl true @impl true
def add_to_index(activity) do def add_to_index(%Activity{object: %Object{} = object} = activity) do
# This will only index public or unlisted notes search_data = object_to_search_data(object)
maybe_search_data = object_to_search_data(activity.object)
if activity.data["type"] == "Create" and maybe_search_data do with {:ok, embedding} <- get_embedding(search_data.content),
with {:ok, embedding} <- get_embedding(maybe_search_data.content),
{:ok, %{status: 200}} <- {:ok, %{status: 200}} <-
QdrantClient.put( QdrantClient.put(
"/collections/posts/points", "/collections/posts/points",
@ -97,9 +96,6 @@ defmodule Pleroma.Search.QdrantSearch do
else else
e -> {:error, e} e -> {:error, e}
end end
else
:ok
end
end end
@impl true @impl true

View file

@ -93,7 +93,7 @@ defmodule Pleroma.Upload do
def store(upload, opts \\ []) do def store(upload, opts \\ []) do
opts = get_opts(opts) opts = get_opts(opts)
with {:ok, upload} <- prepare_upload(upload, opts), with {:ok, %__MODULE__{} = upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"}, upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload), {:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
description = get_description(upload), description = get_description(upload),

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Utils do
dir dir
|> File.ls!() |> File.ls!()
|> Enum.map(&Path.join(dir, &1)) |> Enum.map(&Path.join(dir, &1))
|> Kernel.ParallelCompiler.compile() |> Kernel.ParallelCompiler.compile(return_diagnostics: true)
end end
@doc """ @doc """

View file

@ -1618,6 +1618,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image() defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
defp normalize_image(_), do: nil defp normalize_image(_), do: nil
defp normalize_also_known_as(urls) when is_list(urls), do: urls
defp normalize_also_known_as(url) when is_binary(url), do: [url]
defp normalize_also_known_as(nil), do: []
defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
Map.put(map, "name", description) Map.put(map, "name", description)
end end
@ -1693,7 +1697,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
featured_address: featured_address, featured_address: featured_address,
bio: data["summary"] || "", bio: data["summary"] || "",
actor_type: actor_type, actor_type: actor_type,
also_known_as: Map.get(data, "alsoKnownAs", []), also_known_as: normalize_also_known_as(data["alsoKnownAs"]),
public_key: public_key, public_key: public_key,
inbox: data["inbox"], inbox: data["inbox"],
shared_inbox: shared_inbox, shared_inbox: shared_inbox,

View file

@ -332,21 +332,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()} @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
def announce(actor, object, options \\ []) do def announce(actor, object, options \\ []) do
public? = Keyword.get(options, :public, false) visibility = Keyword.get(options, :visibility, "public")
to = {to, cc} =
cond do if actor.ap_id == Relay.ap_id() do
actor.ap_id == Relay.ap_id() -> {[actor.follower_address], []}
[actor.follower_address] else
Pleroma.Web.CommonAPI.Utils.get_to_and_cc_for_visibility(
public? and Visibility.local_public?(object) -> visibility,
[actor.follower_address, object.data["actor"], Utils.as_local_public()] actor.follower_address,
nil,
public? -> [object.data["actor"]]
[actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()] )
true ->
[actor.follower_address, object.data["actor"]]
end end
{:ok, {:ok,
@ -355,6 +352,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do
"actor" => actor.ap_id, "actor" => actor.ap_id,
"object" => object.data["id"], "object" => object.data["id"],
"to" => to, "to" => to,
"cc" => cc,
"context" => object.data["context"], "context" => object.data["context"],
"type" => "Announce", "type" => "Announce",
"published" => Utils.make_date() "published" => Utils.make_date()

View file

@ -35,32 +35,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("endpoints.json", _), do: %{} def render("endpoints.json", _), do: %{}
def render("service.json", %{user: user}) do def render("service.json", %{user: user}) do
{:ok, _, public_key} = Keys.keys_from_pem(user.keys) Map.merge(common_actor_fields(user), %{
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
endpoints = render("endpoints.json", %{user: user})
%{
"id" => user.ap_id,
"type" => "Application", "type" => "Application",
"following" => "#{user.ap_id}/following",
"followers" => "#{user.ap_id}/followers",
"inbox" => "#{user.ap_id}/inbox",
"outbox" => "#{user.ap_id}/outbox",
"name" => "Pleroma", "name" => "Pleroma",
"summary" => "summary" =>
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.", "An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
"url" => user.ap_id,
"manuallyApprovesFollowers" => false, "manuallyApprovesFollowers" => false,
"publicKey" => %{
"id" => "#{user.ap_id}#main-key",
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
"endpoints" => endpoints,
"invisible" => User.invisible?(user) "invisible" => User.invisible?(user)
} })
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
@ -77,13 +59,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end end
def render("user.json", %{user: user}) do def render("user.json", %{user: user}) do
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
user = User.sanitize_html(user) user = User.sanitize_html(user)
endpoints = render("endpoints.json", %{user: user})
emoji_tags = Transmogrifier.take_emoji_tags(user) emoji_tags = Transmogrifier.take_emoji_tags(user)
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue")) fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
@ -102,25 +79,9 @@ defmodule Pleroma.Web.ActivityPub.UserView do
do: Date.to_iso8601(user.birthday), do: Date.to_iso8601(user.birthday),
else: nil else: nil
%{ Map.merge(common_actor_fields(user), %{
"id" => user.ap_id,
"type" => user.actor_type,
"following" => "#{user.ap_id}/following",
"followers" => "#{user.ap_id}/followers",
"inbox" => "#{user.ap_id}/inbox",
"outbox" => "#{user.ap_id}/outbox",
"featured" => "#{user.ap_id}/collections/featured", "featured" => "#{user.ap_id}/collections/featured",
"preferredUsername" => user.nickname, "preferredUsername" => user.nickname,
"name" => user.name,
"summary" => user.bio,
"url" => user.ap_id,
"manuallyApprovesFollowers" => user.is_locked,
"publicKey" => %{
"id" => "#{user.ap_id}#main-key",
"owner" => user.ap_id,
"publicKeyPem" => public_key
},
"endpoints" => endpoints,
"attachment" => fields, "attachment" => fields,
"tag" => emoji_tags, "tag" => emoji_tags,
# Note: key name is indeed "discoverable" (not an error) # Note: key name is indeed "discoverable" (not an error)
@ -130,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"vcard:bday" => birthday, "vcard:bday" => birthday,
"webfinger" => "acct:#{User.full_nickname(user)}", "webfinger" => "acct:#{User.full_nickname(user)}",
"published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at) "published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)
} })
|> Map.merge( |> Map.merge(
maybe_make_image( maybe_make_image(
&User.avatar_url/2, &User.avatar_url/2,
@ -309,6 +270,33 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
defp common_actor_fields(%User{} = user) do
endpoints = render("endpoints.json", %{user: user})
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
%{
"id" => user.ap_id,
"type" => user.actor_type,
"following" => "#{user.ap_id}/following",
"followers" => "#{user.ap_id}/followers",
"inbox" => "#{user.ap_id}/inbox",
"outbox" => "#{user.ap_id}/outbox",
"name" => user.name,
"summary" => user.bio,
"url" => user.ap_id,
"manuallyApprovesFollowers" => user.is_locked,
"endpoints" => endpoints,
"publicKey" => %{
"id" => "#{user.ap_id}#main-key",
"owner" => user.ap_id,
"publicKeyPem" => public_key
}
}
end
defp maybe_put_total_items(map, false, _total), do: map defp maybe_put_total_items(map, false, _total), do: map
defp maybe_put_total_items(map, true, total) do defp maybe_put_total_items(map, true, total) do

View file

@ -106,7 +106,14 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
end end
defp cast_and_validate(spec, operation, conn, content_type, false = _strict, cast_opts) do defp cast_and_validate(
spec,
operation,
%Conn{} = conn,
content_type,
false = _strict,
cast_opts
) do
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
{:ok, conn} -> {:ok, conn} ->
{:ok, conn} {:ok, conn}

View file

@ -36,7 +36,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
summary: "Create a list", summary: "Create a list",
description: "Fetch the list with the given ID. Used for verifying the title of a list.", description: "Fetch the list with the given ID. Used for verifying the title of a list.",
operationId: "ListController.create", operationId: "ListController.create",
requestBody: create_update_request(), requestBody: create_request(),
security: [%{"oAuth" => ["write:lists"]}], security: [%{"oAuth" => ["write:lists"]}],
responses: %{ responses: %{
200 => Operation.response("List", "application/json", List), 200 => Operation.response("List", "application/json", List),
@ -68,7 +68,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
description: "Change the title of a list", description: "Change the title of a list",
operationId: "ListController.update", operationId: "ListController.update",
parameters: [id_param()], parameters: [id_param()],
requestBody: create_update_request(), requestBody: update_request(),
security: [%{"oAuth" => ["write:lists"]}], security: [%{"oAuth" => ["write:lists"]}],
responses: %{ responses: %{
200 => Operation.response("List", "application/json", List), 200 => Operation.response("List", "application/json", List),
@ -164,14 +164,15 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
) )
end end
defp create_update_request do defp create_request do
request_body( request_body(
"Parameters", "Parameters",
%Schema{ %Schema{
description: "POST body for creating or updating a List", description: "POST body for creating a List",
type: :object, type: :object,
properties: %{ properties: %{
title: %Schema{type: :string, description: "List title"} title: %Schema{type: :string, description: "List title"},
exclusive: %Schema{type: :boolean, description: "Whether members of the list should be removed from the “Home” feed"}
}, },
required: [:title] required: [:title]
}, },
@ -179,6 +180,21 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do
) )
end end
defp update_request do
request_body(
"Parameters",
%Schema{
description: "PUT body for updating a List",
type: :object,
properties: %{
title: %Schema{type: :string, description: "List title"},
exclusive: %Schema{type: :boolean, description: "Whether members of the list should be removed from the “Home” feed"}
}
},
required: true
)
end
defp add_remove_accounts_request(required) when is_boolean(required) do defp add_remove_accounts_request(required) when is_boolean(required) do
request_body( request_body(
"Parameters", "Parameters",

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do defmodule Pleroma.Web.ApiSpec.PleromaUtilOperation do
alias OpenApiSpex.Operation alias OpenApiSpex.Operation
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
%Operation{ %Operation{
tags: ["Custom emojis"], tags: ["Custom emojis"],
summary: "List all custom emojis", summary: "List all custom emojis",
operationId: "UtilController.emoji", operationId: "PleromaAPI.UtilController.emoji",
parameters: [], parameters: [],
responses: %{ responses: %{
200 => 200 =>
@ -48,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
%Operation{ %Operation{
tags: ["Others"], tags: ["Others"],
summary: "Dump frontend configurations", summary: "Dump frontend configurations",
operationId: "UtilController.frontend_configurations", operationId: "PleromaAPI.UtilController.frontend_configurations",
parameters: [], parameters: [],
responses: %{ responses: %{
200 => 200 =>
@ -70,7 +70,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Change account password", summary: "Change account password",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.change_password", operationId: "PleromaAPI.UtilController.change_password",
requestBody: request_body("Parameters", change_password_request(), required: true), requestBody: request_body("Parameters", change_password_request(), required: true),
responses: %{ responses: %{
200 => 200 =>
@ -106,7 +106,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Change account email", summary: "Change account email",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.change_email", operationId: "PleromaAPI.UtilController.change_email",
requestBody: request_body("Parameters", change_email_request(), required: true), requestBody: request_body("Parameters", change_email_request(), required: true),
responses: %{ responses: %{
200 => 200 =>
@ -141,7 +141,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Settings"], tags: ["Settings"],
summary: "Update Notification Settings", summary: "Update Notification Settings",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.update_notification_settings", operationId: "PleromaAPI.UtilController.update_notification_settings",
parameters: [ parameters: [
Operation.parameter( Operation.parameter(
:block_from_strangers, :block_from_strangers,
@ -173,7 +173,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Disable Account", summary: "Disable Account",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.disable_account", operationId: "PleromaAPI.UtilController.disable_account",
parameters: [ parameters: [
Operation.parameter(:password, :query, :string, "Password") Operation.parameter(:password, :query, :string, "Password")
], ],
@ -193,7 +193,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Delete Account", summary: "Delete Account",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.delete_account", operationId: "PleromaAPI.UtilController.delete_account",
parameters: [ parameters: [
Operation.parameter(:password, :query, :string, "Password") Operation.parameter(:password, :query, :string, "Password")
], ],
@ -212,7 +212,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
def captcha_operation do def captcha_operation do
%Operation{ %Operation{
summary: "Get a captcha", summary: "Get a captcha",
operationId: "UtilController.captcha", operationId: "PleromaAPI.UtilController.captcha",
tags: ["Others"], tags: ["Others"],
parameters: [], parameters: [],
responses: %{ responses: %{
@ -226,7 +226,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Move account", summary: "Move account",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.move_account", operationId: "PleromaAPI.UtilController.move_account",
requestBody: request_body("Parameters", move_account_request(), required: true), requestBody: request_body("Parameters", move_account_request(), required: true),
responses: %{ responses: %{
200 => 200 =>
@ -262,7 +262,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "List account aliases", summary: "List account aliases",
security: [%{"oAuth" => ["read:accounts"]}], security: [%{"oAuth" => ["read:accounts"]}],
operationId: "UtilController.list_aliases", operationId: "PleromaAPI.UtilController.list_aliases",
responses: %{ responses: %{
200 => 200 =>
Operation.response("Success", "application/json", %Schema{ Operation.response("Success", "application/json", %Schema{
@ -286,7 +286,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Add an alias to this account", summary: "Add an alias to this account",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.add_alias", operationId: "PleromaAPI.UtilController.add_alias",
requestBody: request_body("Parameters", add_alias_request(), required: true), requestBody: request_body("Parameters", add_alias_request(), required: true),
responses: %{ responses: %{
200 => 200 =>
@ -326,7 +326,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Account credentials"], tags: ["Account credentials"],
summary: "Delete an alias from this account", summary: "Delete an alias from this account",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.delete_alias", operationId: "PleromaAPI.UtilController.delete_alias",
requestBody: request_body("Parameters", delete_alias_request(), required: true), requestBody: request_body("Parameters", delete_alias_request(), required: true),
responses: %{ responses: %{
200 => 200 =>
@ -366,7 +366,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
tags: ["Others"], tags: ["Others"],
summary: "Quick status check on the instance", summary: "Quick status check on the instance",
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.healthcheck", operationId: "PleromaAPI.UtilController.healthcheck",
parameters: [], parameters: [],
responses: %{ responses: %{
200 => Operation.response("Healthy", "application/json", %Schema{type: :object}), 200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
@ -376,52 +376,6 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
} }
end end
def remote_subscribe_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Remote Subscribe",
operationId: "UtilController.remote_subscribe",
parameters: [],
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
}
end
def remote_interaction_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Remote interaction",
operationId: "UtilController.remote_interaction",
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
responses: %{
200 =>
Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
}
}
end
defp remote_interaction_request do
%Schema{
title: "RemoteInteractionRequest",
description: "POST body for remote interaction",
type: :object,
required: [:ap_id, :profile],
properties: %{
ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
profile: %Schema{type: :string, description: "Remote profile webfinger"}
}
}
end
def show_subscribe_form_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Show remote subscribe form",
operationId: "UtilController.show_subscribe_form",
parameters: [],
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
}
end
defp delete_account_request do defp delete_account_request do
%Schema{ %Schema{
title: "AccountDeleteRequest", title: "AccountDeleteRequest",

View file

@ -0,0 +1,99 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.RemoteInteractionOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def remote_interaction_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Remote interaction",
operationId: "RemoteInteractionController.remote_interaction",
requestBody: request_body("Parameters", remote_interaction_request(), required: true),
responses: %{
200 =>
Operation.response("Remote interaction URL", "application/json", %Schema{type: :object})
}
}
end
defp remote_interaction_request do
%Schema{
title: "RemoteInteractionRequest",
description: "POST body for remote interaction",
type: :object,
required: [:ap_id, :profile],
properties: %{
ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"},
profile: %Schema{type: :string, description: "Remote profile webfinger"}
}
}
end
def follow_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Display follow form",
operationId: "RemoteInteractionController.follow",
parameters: [],
responses: %{
200 => Operation.response("Web Page", "text/html", %Schema{type: :string}),
302 => Operation.response("Redirect to the status page", nil, nil)
}
}
end
def do_follow_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Perform follow activity",
operationId: "RemoteInteractionController.do_follow",
parameters: [],
responses: %{
200 => Operation.response("Web page", "text/html", %Schema{type: :string}),
302 => Operation.response("Redirect to the account page", nil, nil)
}
}
end
def authorize_interaction_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Authorize remote interaction",
operationId: "RemoteInteractionController.authorize_interaction",
parameters: [],
responses: %{
302 => Operation.response("Redirect to remote_interaction path", nil, nil)
}
}
end
def show_subscribe_form_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Show remote subscribe form",
operationId: "RemoteInteractionController.show_subscribe_form",
parameters: [],
responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})}
}
end
def remote_subscribe_operation do
%Operation{
tags: ["Remote interaction"],
summary: "Remote Subscribe",
operationId: "RemoteInteractionController.remote_subscribe",
parameters: [],
responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})}
}
end
end

View file

@ -17,11 +17,11 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
def call(conn, errors) do def call(conn, errors) do
errors = errors =
Enum.map(errors, fn Enum.map(errors, fn
%{name: nil, reason: :invalid_enum} = err -> %OpenApiSpex.Cast.Error{name: nil, reason: :invalid_enum} = err ->
%OpenApiSpex.Cast.Error{err | name: err.value} %{err | name: err.value}
%{name: nil} = err -> %OpenApiSpex.Cast.Error{name: nil} = err ->
%OpenApiSpex.Cast.Error{err | name: List.last(err.path)} %{err | name: List.last(err.path)}
err -> err ->
err err

View file

@ -15,12 +15,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do
properties: %{ properties: %{
id: FlakeID, id: FlakeID,
name: %Schema{type: :string, description: "Folder name"}, name: %Schema{type: :string, description: "Folder name"},
emoji: %Schema{type: :string, description: "Folder emoji", nullable: true} emoji: %Schema{type: :string, description: "Folder emoji", nullable: true},
emoji_url: %Schema{
type: :string,
description: "URL of the folder emoji if it's a custom emoji, null otherwise",
nullable: true
}
}, },
example: %{ example: %{
"id" => "9toJCu5YZW7O7gfvH6", "id" => "9toJCu5YZW7O7gfvH6",
"name" => "Read later", "name" => "Read later",
"emoji" => nil "emoji" => nil,
"emoji_url" => nil
} }
}) })
end end

View file

@ -13,7 +13,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do
type: :object, type: :object,
properties: %{ properties: %{
id: %Schema{type: :string, description: "The internal database ID of the list"}, id: %Schema{type: :string, description: "The internal database ID of the list"},
title: %Schema{type: :string, description: "The user-defined title of the list"} title: %Schema{type: :string, description: "The user-defined title of the list"},
exclusive: %Schema{
type: :boolean,
description: "Whether members of the list should be removed from the “Home” feed"
}
}, },
example: %{ example: %{
"id" => "12249", "id" => "12249",

View file

@ -222,8 +222,8 @@ defmodule Pleroma.Web.CommonAPI do
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
object = %Object{} <- Object.normalize(activity, fetch: false), object = %Object{} <- Object.normalize(activity, fetch: false),
{_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)}, {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)},
public = public_announce?(object, params), visibility = announce_visibility(object, params),
{:ok, announce, _} <- Builder.announce(user, object, public: public), {:ok, announce, _} <- Builder.announce(user, object, visibility: visibility),
{:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do
{:ok, activity} {:ok, activity}
else else
@ -407,13 +407,11 @@ defmodule Pleroma.Web.CommonAPI do
end end
end end
defp public_announce?(_, %{visibility: visibility}) def announce_visibility(_, %{visibility: visibility})
when visibility in ~w{public unlisted private direct}, when visibility in ~w{public unlisted private direct local},
do: visibility in ~w(public unlisted) do: visibility
defp public_announce?(object, _) do def announce_visibility(object, _), do: Visibility.get_visibility(object)
Visibility.public?(object)
end
@spec get_visibility(map(), map() | nil, Participation.t() | nil) :: @spec get_visibility(map(), map() | nil, Participation.t() | nil) ::
{String.t() | nil, String.t() | nil} {String.t() | nil, String.t() | nil}

View file

@ -88,7 +88,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> validate() |> validate()
end end
defp listen_object(draft) do defp listen_object(%__MODULE__{} = draft) do
object = object =
draft.params draft.params
|> Map.take([:album, :artist, :title, :length]) |> Map.take([:album, :artist, :title, :length])
@ -99,34 +99,34 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> Map.put("cc", draft.cc) |> Map.put("cc", draft.cc)
|> Map.put("actor", draft.user.ap_id) |> Map.put("actor", draft.user.ap_id)
%__MODULE__{draft | object: object} %{draft | object: object}
end end
defp put_params(draft, params) do defp put_params(%__MODULE__{} = draft, params) do
params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id]) params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id])
%__MODULE__{draft | params: params} %{draft | params: params}
end end
defp status(%{params: %{status: status}} = draft) do defp status(%__MODULE__{params: %{status: status}} = draft) do
%__MODULE__{draft | status: String.trim(status)} %{draft | status: String.trim(status)}
end end
defp summary(%{params: params} = draft) do defp summary(%__MODULE__{params: params} = draft) do
%__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")} %{draft | summary: Map.get(params, :spoiler_text, "")}
end end
defp full_payload(%{status: status, summary: summary} = draft) do defp full_payload(%__MODULE__{status: status, summary: summary} = draft) do
full_payload = String.trim(status <> summary) full_payload = String.trim(status <> summary)
case Utils.validate_character_limit(full_payload, draft.attachments) do case Utils.validate_character_limit(full_payload, draft.attachments) do
:ok -> %__MODULE__{draft | full_payload: full_payload} :ok -> %{draft | full_payload: full_payload}
{:error, message} -> add_error(draft, message) {:error, message} -> add_error(draft, message)
end end
end end
defp attachments(%{params: params} = draft) do defp attachments(%__MODULE__{params: params} = draft) do
attachments = Utils.attachments_from_ids(params, draft.user) attachments = Utils.attachments_from_ids(params, draft.user)
draft = %__MODULE__{draft | attachments: attachments} draft = %{draft | attachments: attachments}
case Utils.validate_attachments_count(attachments) do case Utils.validate_attachments_count(attachments) do
:ok -> draft :ok -> draft
@ -134,9 +134,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
end end
end end
defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: ""}} = draft), do: draft
defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: id}} = draft)
when is_binary(id) do
# If a post was deleted all its activities (except the newly added Delete) are purged too, # If a post was deleted all its activities (except the newly added Delete) are purged too,
# thus lookup by Create db ID will yield nil just as if it never existed in the first place. # thus lookup by Create db ID will yield nil just as if it never existed in the first place.
# #
@ -148,7 +149,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
with %Activity{} = activity <- Activity.get_by_id(id), with %Activity{} = activity <- Activity.get_by_id(id),
true <- Visibility.visible_for_user?(activity, draft.user), true <- Visibility.visible_for_user?(activity, draft.user),
{_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do {_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do
%__MODULE__{draft | in_reply_to: activity} %{draft | in_reply_to: activity}
else else
nil -> nil ->
add_error(draft, dgettext("errors", "Cannot reply to a deleted status")) add_error(draft, dgettext("errors", "Cannot reply to a deleted status"))
@ -166,40 +167,43 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
end end
end end
defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do defp in_reply_to(
%__MODULE__{draft | in_reply_to: in_reply_to} %__MODULE__{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft
) do
%{draft | in_reply_to: in_reply_to}
end end
defp in_reply_to(draft), do: draft defp in_reply_to(draft), do: draft
defp quote_post(%{params: %{quoted_status_id: id}} = draft) when not_empty_string(id) do defp quote_post(%__MODULE__{params: %{quoted_status_id: id}} = draft)
when not_empty_string(id) do
case Activity.get_by_id_with_object(id) do case Activity.get_by_id_with_object(id) do
%Activity{} = activity -> %Activity{} = activity ->
%__MODULE__{draft | quote_post: activity} %{draft | quote_post: activity}
_ -> _ ->
draft draft
end end
end end
defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do defp quote_post(%__MODULE__{params: %{quote_id: id}} = draft) when not_empty_string(id) do
quote_post(%{draft | params: Map.put(draft.params, :quoted_status_id, id)}) quote_post(%{draft | params: Map.put(draft.params, :quoted_status_id, id)})
end end
defp quote_post(draft), do: draft defp quote_post(draft), do: draft
defp in_reply_to_conversation(draft) do defp in_reply_to_conversation(%__MODULE__{} = draft) do
in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id]) in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id])
%__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} %{draft | in_reply_to_conversation: in_reply_to_conversation}
end end
defp visibility(%{params: params} = draft) do defp visibility(%__MODULE__{params: params} = draft) do
case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do
{visibility, "direct"} when visibility != "direct" -> {visibility, "direct"} when visibility != "direct" ->
add_error(draft, dgettext("errors", "The message visibility must be direct")) add_error(draft, dgettext("errors", "The message visibility must be direct"))
{visibility, _} -> {visibility, _} ->
%__MODULE__{draft | visibility: visibility} %{draft | visibility: visibility}
end end
end end
@ -215,7 +219,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
false false
end end
defp quoting_visibility(%{quote_post: %Activity{}} = draft) do defp quoting_visibility(%__MODULE__{quote_post: %Activity{}} = draft) do
with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false), with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false),
true <- can_quote?(draft, object, Visibility.get_visibility(object)) do true <- can_quote?(draft, object, Visibility.get_visibility(object)) do
draft draft
@ -226,24 +230,24 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
defp quoting_visibility(draft), do: draft defp quoting_visibility(draft), do: draft
defp expires_at(draft) do defp expires_at(%__MODULE__{} = draft) do
case CommonAPI.check_expiry_date(draft.params[:expires_in]) do case CommonAPI.check_expiry_date(draft.params[:expires_in]) do
{:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at} {:ok, expires_at} -> %{draft | expires_at: expires_at}
{:error, message} -> add_error(draft, message) {:error, message} -> add_error(draft, message)
end end
end end
defp poll(draft) do defp poll(%__MODULE__{} = draft) do
case Utils.make_poll_data(draft.params) do case Utils.make_poll_data(draft.params) do
{:ok, {poll, poll_emoji}} -> {:ok, {poll, poll_emoji}} ->
%__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)} %{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)}
{:error, message} -> {:error, message} ->
add_error(draft, message) add_error(draft, message)
end end
end end
defp content(%{mentions: mentions} = draft) do defp content(%__MODULE__{mentions: mentions} = draft) do
{content_html, mentioned_users, tags} = Utils.make_content_html(draft) {content_html, mentioned_users, tags} = Utils.make_content_html(draft)
mentioned_ap_ids = mentioned_ap_ids =
@ -254,25 +258,25 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> Kernel.++(mentioned_ap_ids) |> Kernel.++(mentioned_ap_ids)
|> Utils.get_addressed_users(draft.params[:to]) |> Utils.get_addressed_users(draft.params[:to])
%__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags} %{draft | content_html: content_html, mentions: mentions, tags: tags}
end end
defp to_and_cc(draft) do defp to_and_cc(%__MODULE__{} = draft) do
{to, cc} = Utils.get_to_and_cc(draft) {to, cc} = Utils.get_to_and_cc(draft)
%__MODULE__{draft | to: to, cc: cc} %{draft | to: to, cc: cc}
end end
defp context(draft) do defp context(%__MODULE__{} = draft) do
context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation) context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation)
%__MODULE__{draft | context: context} %{draft | context: context}
end end
defp sensitive(draft) do defp sensitive(%__MODULE__{} = draft) do
sensitive = draft.params[:sensitive] sensitive = draft.params[:sensitive]
%__MODULE__{draft | sensitive: sensitive} %{draft | sensitive: sensitive}
end end
defp language(draft) do defp language(%__MODULE__{} = draft) do
language = language =
with language <- draft.params[:language], with language <- draft.params[:language],
true <- good_locale_code?(language) do true <- good_locale_code?(language) do
@ -281,10 +285,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
_ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary) _ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary)
end end
%__MODULE__{draft | language: language} %{draft | language: language}
end end
defp object(draft) do defp object(%__MODULE__{} = draft) do
emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji) emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji)
# Sometimes people create posts with subject containing emoji, # Sometimes people create posts with subject containing emoji,
@ -325,15 +329,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
|> Map.put("generator", draft.params[:generator]) |> Map.put("generator", draft.params[:generator])
|> Map.put("language", draft.language) |> Map.put("language", draft.language)
%__MODULE__{draft | object: object} %{draft | object: object}
end end
defp preview?(draft) do defp preview?(%__MODULE__{} = draft) do
preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview]) preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview])
%__MODULE__{draft | preview?: preview?} %{draft | preview?: preview?}
end end
defp changes(draft) do defp changes(%__MODULE__{} = draft) do
direct? = draft.visibility == "direct" direct? = draft.visibility == "direct"
additional = %{"cc" => draft.cc, "directMessage" => direct?} additional = %{"cc" => draft.cc, "directMessage" => direct?}
@ -353,14 +357,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
} }
|> Utils.maybe_add_list_data(draft.user, draft.visibility) |> Utils.maybe_add_list_data(draft.user, draft.visibility)
%__MODULE__{draft | changes: changes} %{draft | changes: changes}
end end
defp with_valid(%{valid?: true} = draft, func), do: func.(draft) defp with_valid(%{valid?: true} = draft, func), do: func.(draft)
defp with_valid(draft, _func), do: draft defp with_valid(draft, _func), do: draft
defp add_error(draft, message) do defp add_error(%__MODULE__{} = draft, message) do
%__MODULE__{draft | valid?: false, errors: [message | draft.errors]} %{draft | valid?: false, errors: [message | draft.errors]}
end end
defp validate(%{valid?: true} = draft), do: {:ok, draft} defp validate(%{valid?: true} = draft), do: {:ok, draft}

View file

@ -75,48 +75,70 @@ defmodule Pleroma.Web.CommonAPI.Utils do
{Enum.map(participation.recipients, & &1.ap_id), []} {Enum.map(participation.recipients, & &1.ap_id), []}
end end
def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do def get_to_and_cc(%{visibility: visibility} = draft) do
# If the OP is a DM already, add the implicit actor
mentions =
if visibility == "direct" && draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do
Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions])
else
draft.mentions
end
get_to_and_cc_for_visibility(
visibility,
draft.user.follower_address,
draft.in_reply_to && draft.in_reply_to.data["actor"],
mentions
)
end
def get_to_and_cc_for_visibility("public", follower_collection, parent_actor, mentions) do
scope_addr = Pleroma.Constants.as_public()
to = to =
case visibility do if parent_actor,
"public" -> [Pleroma.Constants.as_public() | draft.mentions] do: Enum.uniq([parent_actor, scope_addr | mentions]),
"local" -> [Utils.as_local_public() | draft.mentions] else: [scope_addr | mentions]
{to, [follower_collection]}
end end
cc = [draft.user.follower_address] def get_to_and_cc_for_visibility("local", follower_collection, parent_actor, mentions) do
recipients =
if parent_actor,
do: Enum.uniq([parent_actor | mentions]),
else: mentions
if draft.in_reply_to do to = [
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} Utils.as_local_public()
else | Enum.filter(recipients, fn addr ->
{to, cc} String.starts_with?(addr, Pleroma.Web.Endpoint.url() <> "/")
end end)
]
{to, [follower_collection]}
end end
def get_to_and_cc(%{visibility: "unlisted"} = draft) do def get_to_and_cc_for_visibility("unlisted", follower_collection, parent_actor, mentions) do
to = [draft.user.follower_address | draft.mentions] to =
cc = [Pleroma.Constants.as_public()] if parent_actor,
do: Enum.uniq([parent_actor, follower_collection | mentions]),
else: [follower_collection | mentions]
if draft.in_reply_to do {to, [Pleroma.Constants.as_public()]}
{Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc}
else
{to, cc}
end
end end
def get_to_and_cc(%{visibility: "private"} = draft) do def get_to_and_cc_for_visibility("private", follower_collection, _, mentions) do
{to, cc} = get_to_and_cc(struct(draft, visibility: "direct")) {[follower_collection | mentions], []}
{[draft.user.follower_address | to], cc}
end end
def get_to_and_cc(%{visibility: "direct"} = draft) do def get_to_and_cc_for_visibility("direct", _, _, mentions) do
# If the OP is a DM already, add the implicit actor. {mentions, []}
if draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do
{Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []}
else
{draft.mentions, []}
end
end end
def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []} def get_to_and_cc_for_visibility({:list, _}, _, _, mentions) do
{mentions, []}
end
def get_addressed_users(_, to) when is_list(to) do def get_addressed_users(_, to) when is_list(to) do
User.get_ap_ids_by_nicknames(to) User.get_ap_ids_by_nicknames(to)

View file

@ -20,6 +20,7 @@ defmodule Pleroma.Web.EmbedController do
conn conn
|> delete_resp_header("x-frame-options") |> delete_resp_header("x-frame-options")
|> delete_resp_header("content-security-policy") |> delete_resp_header("content-security-policy")
|> put_layout({Pleroma.Web.LayoutView, :embed})
|> render("show.html", |> render("show.html",
activity: activity, activity: activity,
author: User.sanitize_html(author), author: User.sanitize_html(author),

View file

@ -29,9 +29,18 @@ defmodule Pleroma.Web.Fallback.RedirectController do
) )
end end
def live_dashboard(conn, _params) do def live_dashboard(conn, %{"path" => path}) do
query_params = conn.query_string
redirect_path =
if query_params == "" do
"/pleroma/live_dashboard/#{path}"
else
"/pleroma/live_dashboard/#{path}?#{query_params}"
end
conn conn
|> redirect(to: "/pleroma/live_dashboard") |> redirect(to: redirect_path)
end end
def redirector(conn, _params, code \\ 200) do def redirector(conn, _params, code \\ 200) do

View file

@ -26,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Plugs.RateLimiter alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.Registration
alias Pleroma.Web.Utils.Params alias Pleroma.Web.Utils.Params
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
@ -111,8 +111,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
_params _params
) do ) do
with :ok <- validate_email_param(params), with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, params), :ok <- Registration.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params), {:ok, user} <- Registration.register_user(params),
{_, {:ok, token}} <- {_, {:ok, token}} <-
{:login, OAuthController.login(user, app, app.scopes)} do {:login, OAuthController.login(user, app, app.scopes)} do
OAuthController.after_token_exchange(conn, %{user: user, token: token}) OAuthController.after_token_exchange(conn, %{user: user, token: token})

View file

@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.Registration
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
def password_reset(conn, params) do def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"] nickname_or_email = params["email"] || params["nickname"]
TwitterAPI.password_reset(nickname_or_email) Registration.password_reset(nickname_or_email)
json_response(conn, :no_content, "") json_response(conn, :no_content, "")
end end

View file

@ -28,27 +28,27 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
# POST /api/v1/lists # POST /api/v1/lists
def create( def create(
%{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{title: title}}}} = %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} =
conn, conn,
_ _
) do ) do
with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(params, user) do
render(conn, "show.json", list: list) render(conn, "show.json", list: list)
end end
end end
# GET /api/v1/lists/:idOB # GET /api/v1/lists/:id
def show(%{assigns: %{list: list}} = conn, _) do def show(%{assigns: %{list: list}} = conn, _) do
render(conn, "show.json", list: list) render(conn, "show.json", list: list)
end end
# PUT /api/v1/lists/:id # PUT /api/v1/lists/:id
def update( def update(
%{assigns: %{list: list}, private: %{open_api_spex: %{body_params: %{title: title}}}} = %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: params}}} =
conn, conn,
_ _
) do ) do
with {:ok, list} <- Pleroma.List.rename(list, title) do with {:ok, list} <- Pleroma.List.update(list, params) do
render(conn, "show.json", list: list) render(conn, "show.json", list: list)
end end
end end

View file

@ -81,10 +81,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action in [:pin, :unpin]) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action in [:pin, :unpin])
# Note: scope not present in Mastodon: read:bookmarks
plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks) plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks)
# Note: scope not present in Mastodon: write:bookmarks
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]

View file

@ -45,6 +45,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> User.followed_hashtags() |> User.followed_hashtags()
|> Enum.map(& &1.id) |> Enum.map(& &1.id)
excluded_list_members =
user
|> Pleroma.List.get_exclusive_list_members()
params = params =
params params
|> Map.put(:type, ["Create", "Announce"]) |> Map.put(:type, ["Create", "Announce"])
@ -58,7 +62,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
|> Map.delete(:local) |> Map.delete(:local)
activities = activities =
[user.ap_id | User.following(user)] [user.ap_id | User.following(user) -- excluded_list_members]
|> ActivityPub.fetch_activities(params) |> ActivityPub.fetch_activities(params)
|> Enum.reverse() |> Enum.reverse()

View file

@ -13,7 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.ListView do
def render("show.json", %{list: list}) do def render("show.json", %{list: list}) do
%{ %{
id: to_string(list.id), id: to_string(list.id),
title: list.title title: list.title,
exclusive: list.exclusive
} }
end end
end end

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.PasswordController do defmodule Pleroma.Web.OAuth.PasswordController do
@moduledoc """ @moduledoc """
The module contains functions for password reset. The module contains functions for password reset.
""" """
@ -16,7 +16,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
alias Pleroma.PasswordResetToken alias Pleroma.PasswordResetToken
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.Registration
plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request) plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request)
@ -24,7 +24,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do
def request(conn, params) do def request(conn, params) do
nickname_or_email = params["email"] || params["nickname"] nickname_or_email = params["email"] || params["nickname"]
TwitterAPI.password_reset(nickname_or_email) Registration.password_reset(nickname_or_email)
json_response(conn, :no_content, "") json_response(conn, :no_content, "")
end end

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.PasswordView do defmodule Pleroma.Web.OAuth.PasswordView do
use Pleroma.Web, :view use Pleroma.Web, :view
import Phoenix.HTML.Form import Phoenix.HTML.Form
alias Pleroma.Web.Gettext alias Pleroma.Web.Gettext

View file

@ -2,13 +2,13 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.Controller do defmodule Pleroma.Web.OAuth.TokenController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.TokenView
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.TwitterAPI.TokenView
require Logger require Logger

View file

@ -2,12 +2,12 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TokenView do defmodule Pleroma.Web.OAuth.TokenView do
use Pleroma.Web, :view use Pleroma.Web, :view
def render("index.json", %{tokens: tokens}) do def render("index.json", %{tokens: tokens}) do
tokens tokens
|> render_many(Pleroma.Web.TwitterAPI.TokenView, "show.json") |> render_many(Pleroma.Web.OAuth.TokenView, "show.json")
|> Enum.filter(&Enum.any?/1) |> Enum.filter(&Enum.any?/1)
end end

View file

@ -10,10 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderController do
plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(Pleroma.Web.ApiSpec.CastAndValidate)
# Note: scope not present in Mastodon: read:bookmarks
plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index) plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index)
# Note: scope not present in Mastodon: write:bookmarks
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete] %{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete]

View file

@ -0,0 +1,52 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.PasswordController do
@moduledoc """
The module contains functions for password reset.
"""
use Pleroma.Web, :controller
require Logger
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.PasswordResetToken
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.Registration
plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request)
@doc "POST /auth/password"
def request(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
Registration.password_reset(nickname_or_email)
json_response(conn, :no_content, "")
end
def reset(conn, %{"token" => token}) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
false <- PasswordResetToken.expired?(token),
%User{} = user <- User.get_cached_by_id(token.user_id) do
render(conn, "reset.html", %{
token: token,
user: user
})
else
_e -> render(conn, "invalid_token.html")
end
end
def do_reset(conn, %{"data" => data}) do
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
render(conn, "reset_success.html")
else
_e -> render(conn, "reset_failed.html")
end
end
end

View file

@ -0,0 +1,59 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.TokenController do
use Pleroma.Web, :controller
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.PleromaAPI.TokenView
alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
plug(:skip_auth when action == :confirm_email)
plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid),
true <- user.local and !user.is_confirmed and user.confirmation_token == token,
{:ok, _} <- User.confirm(user) do
redirect(conn, to: "/")
end
end
def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
with oauth_tokens <- Token.get_user_tokens(user) do
conn
|> put_view(TokenView)
|> render("index.json", %{tokens: oauth_tokens})
end
end
def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
Token.delete_user_token(user, id)
json_reply(conn, 201, "")
end
defp errors(conn, {:param_cast, _}) do
conn
|> put_status(400)
|> json("Invalid parameters")
end
defp errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
end
defp json_reply(conn, status, json) do
conn
|> put_resp_content_type("application/json")
|> send_resp(status, json)
end
end

View file

@ -2,12 +2,11 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.UtilController do defmodule Pleroma.Web.PleromaAPI.UtilController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
require Logger require Logger
alias Pleroma.Activity
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Healthcheck alias Pleroma.Healthcheck
@ -17,19 +16,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
plug( plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
Pleroma.Web.ApiSpec.CastAndValidate,
[replace_params: false]
when action != :remote_subscribe and action != :show_subscribe_form
)
plug(
Pleroma.Web.Plugs.FederatingPlug
when action == :remote_subscribe
when action == :show_subscribe_form
)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
@ -54,125 +42,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
] ]
) )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaUtilOperation
def show_subscribe_form(conn, %{"nickname" => nick}) 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
_e ->
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"remote follow error message - user not found",
"Could not find user"
)
})
end
end
def show_subscribe_form(conn, %{"status_id" => id}) do
with %Activity{} = activity <- Activity.get_by_id(id),
{:ok, ap_id} <- get_ap_id(activity),
%User{} = user <- User.get_cached_by_ap_id(activity.actor),
avatar = User.avatar_url(user) do
conn
|> render("status_interact.html", %{
status_link: ap_id,
status_id: id,
nickname: user.nickname,
avatar: avatar,
error: false
})
else
_e ->
render(conn, "status_interact.html", %{
status_id: id,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"status interact error message - status not found",
"Could not find status"
)
})
end
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
show_subscribe_form(conn, %{"nickname" => nick})
end
def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do
show_subscribe_form(conn, %{"status_id" => id})
end
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
conn
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
_e ->
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"remote follow error message - unknown error",
"Something went wrong."
)
})
end
end
def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%Activity{} = activity <- Activity.get_by_id(id),
{:ok, ap_id} <- get_ap_id(activity) do
conn
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
_e ->
render(conn, "status_interact.html", %{
status_id: id,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"status interact error message - unknown error",
"Something went wrong."
)
})
end
end
def remote_interaction(
%{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn,
_params
) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
conn
|> json(%{url: String.replace(template, "{uri}", ap_id)})
else
_e -> json(conn, %{error: "Couldn't find user"})
end
end
defp get_ap_id(activity) do
object = Pleroma.Object.normalize(activity, fetch: false)
case object do
%{data: %{"id" => ap_id}} -> {:ok, ap_id}
_ -> {:no_ap_id, nil}
end
end
def frontend_configurations(conn, _params) do def frontend_configurations(conn, _params) do
render(conn, "frontend_configurations.json") render(conn, "frontend_configurations.json")

View file

@ -9,11 +9,13 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do
alias Pleroma.Emoji alias Pleroma.Emoji
def render("show.json", %{folder: %BookmarkFolder{} = folder}) do def render("show.json", %{folder: %BookmarkFolder{} = folder}) do
{emoji, emoji_url} = get_emoji(folder.emoji)
%{ %{
id: folder.id |> to_string(), id: folder.id |> to_string(),
name: folder.name, name: folder.name,
emoji: folder.emoji, emoji: emoji,
emoji_url: get_emoji_url(folder.emoji) emoji_url: emoji_url
} }
end end
@ -21,20 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do
render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders)) render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders))
end end
defp get_emoji_url(nil) do defp get_emoji(nil), do: {nil, nil}
nil
end
defp get_emoji_url(emoji) do defp get_emoji(emoji) do
if Emoji.unicode?(emoji) do if Emoji.unicode?(emoji) do
nil {emoji, nil}
else else
emoji = Emoji.get(emoji) case Emoji.get(emoji) do
nil -> {nil, nil}
if emoji != nil do emoji_data -> {emoji, Emoji.local_url(emoji_data.file)}
Emoji.local_url(emoji.file)
else
nil
end end
end end
end end

View file

@ -0,0 +1,13 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.UtilView do
use Pleroma.Web, :view
alias Pleroma.Config
def render("frontend_configurations.json", _) do
Config.get(:frontend_configurations, %{})
|> Enum.into(%{})
end
end

View file

@ -4,7 +4,7 @@
defmodule Pleroma.Web.Plugs.InstanceStatic do defmodule Pleroma.Web.Plugs.InstanceStatic do
require Pleroma.Constants require Pleroma.Constants
import Plug.Conn, only: [put_resp_header: 3] import Plug.Conn, only: [put_resp_header: 3, put_status: 2, send_resp: 3, halt: 1]
@moduledoc """ @moduledoc """
This is a shim to call `Plug.Static` but with runtime `from` configuration. This is a shim to call `Plug.Static` but with runtime `from` configuration.
@ -51,10 +51,37 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do
|> Map.put(:from, from) |> Map.put(:from, from)
|> Map.put(:content_types, false) |> Map.put(:content_types, false)
conn = set_content_type(conn, conn.request_path) conn =
conn
|> set_content_type(conn.request_path)
|> Plug.Static.call(opts)
# Call Plug.Static with our sanitized content-type if conn.halted do
Plug.Static.call(conn, opts) conn
else
path = String.trim_leading(conn.request_path, "/")
if not File.exists?(file_path(path)) do
conn
|> put_status(:not_found)
|> send_404()
|> halt()
else
conn
end
end
end
defp send_404(conn) do
if String.ends_with?(String.downcase(conn.request_path), ".json") do
conn
|> put_resp_header("content-type", "application/json")
|> send_resp(404, Jason.encode!(%{error: "not found"}))
else
conn
|> put_resp_header("content-type", "text/plain")
|> send_resp(404, "Not found")
end
end end
defp set_content_type(conn, "/emoji/" <> filepath) do defp set_content_type(conn, "/emoji/" <> filepath) do

View file

@ -67,6 +67,8 @@ defmodule Pleroma.Web.Plugs.RateLimiter do
import Plug.Conn import Plug.Conn
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Config.Holder
alias Pleroma.EctoType.Config.RateLimit
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor
@ -143,7 +145,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter do
def action_settings(plug_opts) do def action_settings(plug_opts) do
with limiter_name when is_atom(limiter_name) <- plug_opts[:name], with limiter_name when is_atom(limiter_name) <- plug_opts[:name],
limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do {:ok, limits} <- fetch_and_normalize_limits(limiter_name) do
bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name) bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name)
%{ %{
@ -151,6 +153,72 @@ defmodule Pleroma.Web.Plugs.RateLimiter do
limits: limits, limits: limits,
opts: plug_opts opts: plug_opts
} }
else
:disabled -> nil
end
end
defp fetch_and_normalize_limits(limiter_name) do
limits = Config.get([:rate_limit, limiter_name])
case normalize_limits(limits) do
{:ok, limits} ->
{:ok, limits}
:disabled ->
:disabled
:error ->
default_limits =
Holder.default_config(:pleroma, :rate_limit)
|> get_default_limits(limiter_name)
case normalize_limits(default_limits) do
{:ok, normalized_limits} ->
warn_invalid_limits_once(limiter_name, limits)
{:ok, normalized_limits}
_ ->
warn_invalid_limits_once(limiter_name, limits)
:disabled
end
end
end
defp get_default_limits(%{} = rate_limit, limiter_name), do: Map.get(rate_limit, limiter_name)
defp get_default_limits(rate_limit, limiter_name) when is_list(rate_limit) do
if Keyword.keyword?(rate_limit) do
Keyword.get(rate_limit, limiter_name)
else
nil
end
end
defp get_default_limits(_, _), do: nil
@invalid_limits_warned_key {__MODULE__, :invalid_limits_warned}
defp warn_invalid_limits_once(limiter_name, limits) do
warned = :persistent_term.get(@invalid_limits_warned_key, MapSet.new())
if MapSet.member?(warned, limiter_name) do
:ok
else
:persistent_term.put(@invalid_limits_warned_key, MapSet.put(warned, limiter_name))
Logger.warning(
"Invalid rate limiter config for #{inspect(limiter_name)}: #{inspect(limits)}. Falling back to defaults or disabling this limiter."
)
end
end
defp normalize_limits(nil), do: :disabled
defp normalize_limits(limits) do
case RateLimit.cast(limits) do
{:ok, normalized_limits} -> {:ok, normalized_limits}
:error -> :error
end end
end end

View file

@ -5,9 +5,9 @@
defmodule Pleroma.Web.Preload.Providers.Instance do defmodule Pleroma.Web.Preload.Providers.Instance do
alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.MastodonAPI.InstanceView
alias Pleroma.Web.Nodeinfo.Nodeinfo alias Pleroma.Web.Nodeinfo.Nodeinfo
alias Pleroma.Web.PleromaAPI.UtilView
alias Pleroma.Web.Plugs.InstanceStatic alias Pleroma.Web.Plugs.InstanceStatic
alias Pleroma.Web.Preload.Providers.Provider alias Pleroma.Web.Preload.Providers.Provider
alias Pleroma.Web.TwitterAPI.UtilView
@behaviour Provider @behaviour Provider
@instance_url "/api/v1/instance" @instance_url "/api/v1/instance"

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do defmodule Pleroma.Web.Registration do
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
alias Pleroma.Emails.Mailer alias Pleroma.Emails.Mailer

View file

@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
require Logger require Logger
@ -14,18 +14,27 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.Auth.TOTPAuthenticator
alias Pleroma.Web.Auth.WrapperAuthenticator alias Pleroma.Web.Auth.WrapperAuthenticator
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.WebFinger
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"] @status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
plug(
Pleroma.Web.ApiSpec.CastAndValidate,
[replace_params: false]
when action == :remote_interaction
)
plug(Pleroma.Web.Plugs.FederatingPlug) plug(Pleroma.Web.Plugs.FederatingPlug)
# Note: follower can submit the form (with password auth) not being signed in (having no token) # Note: follower can submit the form (with password auth) not being signed in (having no token)
plug( plug(
Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
when action in [:do_follow] when action == :do_follow
) )
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.RemoteInteractionOperation
# GET /ostatus_subscribe # GET /ostatus_subscribe
# #
def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
@ -125,7 +134,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
# #
def authorize_interaction(conn, %{"uri" => uri}) do def authorize_interaction(conn, %{"uri" => uri}) do
conn conn
|> redirect(to: Routes.remote_follow_path(conn, :follow, %{acct: uri})) |> redirect(to: Routes.remote_interaction_path(conn, :follow, %{acct: uri}))
end end
defp handle_follow_error(conn, {:mfa_token, followee, _} = _) do defp handle_follow_error(conn, {:mfa_token, followee, _} = _) do
@ -162,4 +171,122 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
Logger.debug("Remote follow failed with error #{inspect(error)}") Logger.debug("Remote follow failed with error #{inspect(error)}")
render(conn, "followed.html", %{error: "Something went wrong."}) render(conn, "followed.html", %{error: "Something went wrong."})
end end
def show_subscribe_form(conn, %{"nickname" => nick}) 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
_e ->
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"remote follow error message - user not found",
"Could not find user"
)
})
end
end
def show_subscribe_form(conn, %{"status_id" => id}) do
with %Activity{} = activity <- Activity.get_by_id(id),
{:ok, ap_id} <- get_ap_id(activity),
%User{} = user <- User.get_cached_by_ap_id(activity.actor),
avatar = User.avatar_url(user) do
conn
|> render("status_interact.html", %{
status_link: ap_id,
status_id: id,
nickname: user.nickname,
avatar: avatar,
error: false
})
else
_e ->
render(conn, "status_interact.html", %{
status_id: id,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"status interact error message - status not found",
"Could not find status"
)
})
end
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
show_subscribe_form(conn, %{"nickname" => nick})
end
def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do
show_subscribe_form(conn, %{"status_id" => id})
end
def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do
conn
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
_e ->
render(conn, "subscribe.html", %{
nickname: nick,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"remote follow error message - unknown error",
"Something went wrong."
)
})
end
end
def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile),
%Activity{} = activity <- Activity.get_by_id(id),
{:ok, ap_id} <- get_ap_id(activity) do
conn
|> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id))
else
_e ->
render(conn, "status_interact.html", %{
status_id: id,
avatar: nil,
error:
Pleroma.Web.Gettext.dpgettext(
"static_pages",
"status interact error message - unknown error",
"Something went wrong."
)
})
end
end
def remote_interaction(
%{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn,
_params
) do
with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do
conn
|> json(%{url: String.replace(template, "{uri}", ap_id)})
else
_e -> json(conn, %{error: "Couldn't find user"})
end
end
defp get_ap_id(activity) do
object = Pleroma.Object.normalize(activity, fetch: false)
case object do
%{data: %{"id" => ap_id}} -> {:ok, ap_id}
_ -> {:no_ap_id, nil}
end
end
end end

View file

@ -2,9 +2,11 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionView do
use Pleroma.Web, :view use Pleroma.Web, :view
import Phoenix.HTML
import Phoenix.HTML.Form import Phoenix.HTML.Form
import Phoenix.HTML.Link
alias Pleroma.Web.Gettext alias Pleroma.Web.Gettext
def avatar_url(user) do def avatar_url(user) do

View file

@ -226,23 +226,28 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Web.Plugs.StaticFEPlug) plug(Pleroma.Web.Plugs.StaticFEPlug)
end end
scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do scope "/api/v1/pleroma", Pleroma.Web.OAuth do
pipe_through(:pleroma_api) pipe_through(:pleroma_api)
get("/password_reset/:token", PasswordController, :reset, as: :reset_password) get("/password_reset/:token", PasswordController, :reset, as: :reset_password)
post("/password_reset", PasswordController, :do_reset, as: :reset_password) post("/password_reset", PasswordController, :do_reset, as: :reset_password)
get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck)
post("/remote_interaction", UtilController, :remote_interaction)
end end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:pleroma_api) pipe_through(:pleroma_api)
get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck)
get("/federation_status", InstancesController, :show) get("/federation_status", InstancesController, :show)
end end
scope "/api/v1/pleroma", Pleroma.Web.RemoteInteraction do
pipe_through(:pleroma_api)
post("/remote_interaction", RemoteInteractionController, :remote_interaction)
end
scope "/api/v1/pleroma", Pleroma.Web do scope "/api/v1/pleroma", Pleroma.Web do
pipe_through(:pleroma_api) pipe_through(:pleroma_api)
post("/uploader_callback/:upload_path", UploaderController, :callback) post("/uploader_callback/:upload_path", UploaderController, :callback)
@ -484,18 +489,18 @@ defmodule Pleroma.Web.Router do
end end
end end
scope "/", Pleroma.Web.TwitterAPI do scope "/", Pleroma.Web.RemoteInteraction do
pipe_through(:pleroma_html) pipe_through(:pleroma_html)
post("/main/ostatus", UtilController, :remote_subscribe) post("/main/ostatus", RemoteInteractionController, :remote_subscribe)
get("/main/ostatus", UtilController, :show_subscribe_form) get("/main/ostatus", RemoteInteractionController, :show_subscribe_form)
get("/ostatus_subscribe", RemoteFollowController, :follow) get("/ostatus_subscribe", RemoteInteractionController, :follow)
post("/ostatus_subscribe", RemoteFollowController, :do_follow) post("/ostatus_subscribe", RemoteInteractionController, :do_follow)
get("/authorize_interaction", RemoteFollowController, :authorize_interaction) get("/authorize_interaction", RemoteInteractionController, :authorize_interaction)
end end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.PleromaAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
post("/change_email", UtilController, :change_email) post("/change_email", UtilController, :change_email)
@ -853,7 +858,7 @@ defmodule Pleroma.Web.Router do
scope "/api", Pleroma.Web do scope "/api", Pleroma.Web do
pipe_through(:config) pipe_through(:config)
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations) get("/pleroma/frontend_configurations", PleromaAPI.UtilController, :frontend_configurations)
end end
scope "/api", Pleroma.Web do scope "/api", Pleroma.Web do
@ -861,7 +866,7 @@ defmodule Pleroma.Web.Router do
get( get(
"/account/confirm_email/:user_id/:token", "/account/confirm_email/:user_id/:token",
TwitterAPI.Controller, OAuth.TokenController,
:confirm_email, :confirm_email,
as: :confirm_email as: :confirm_email
) )
@ -873,11 +878,11 @@ defmodule Pleroma.Web.Router do
get("/openapi", OpenApiSpex.Plug.RenderSpec, []) get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
end end
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do scope "/api", Pleroma.Web, as: :authenticated_pleroma_api do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) get("/oauth_tokens", OAuth.TokenController, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) delete("/oauth_tokens/:id", OAuth.TokenController, :revoke_token)
end end
scope "/", Pleroma.Web do scope "/", Pleroma.Web do
@ -1026,7 +1031,9 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web do scope "/", Pleroma.Web do
pipe_through(:pleroma_html) pipe_through(:pleroma_html)
post("/auth/password", TwitterAPI.PasswordController, :request) post("/auth/password", OAuth.PasswordController, :request)
get("/embed/:id", EmbedController, :show)
end end
scope "/proxy/", Pleroma.Web do scope "/proxy/", Pleroma.Web do
@ -1088,7 +1095,7 @@ defmodule Pleroma.Web.Router do
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, []) match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, [])
get("/api/*path", RedirectController, :api_not_implemented) get("/api/*path", RedirectController, :api_not_implemented)
get("/phoenix/live_dashboard", RedirectController, :live_dashboard) get("/phoenix/live_dashboard/*path", RedirectController, :live_dashboard)
get("/*path", RedirectController, :redirector_with_preload) get("/*path", RedirectController, :redirector_with_preload)
options("/*path", RedirectController, :empty) options("/*path", RedirectController, :empty)

View file

@ -4,7 +4,7 @@
<h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %></h2> <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %></h2>
<img height="128" width="128" src="<%= avatar_url(@followee) %>"> <img height="128" width="128" src="<%= avatar_url(@followee) %>">
<p><%= @followee.nickname %></p> <p><%= @followee.nickname %></p>
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %> <%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "user"], fn f -> %>
<%= hidden_input f, :id, value: @followee.id %> <%= hidden_input f, :id, value: @followee.id %>
<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %>
<% end %> <% end %>

View file

@ -4,7 +4,7 @@
<h2><%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %></h2> <h2><%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %></h2>
<p><%= @followee.nickname %></p> <p><%= @followee.nickname %></p>
<img height="128" width="128" src="<%= avatar_url(@followee) %>"> <img height="128" width="128" src="<%= avatar_url(@followee) %>">
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %> <%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
<%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %> <%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
<br> <br>
<%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %> <%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %>

View file

@ -4,7 +4,7 @@
<h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %></h2> <h2><%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %></h2>
<p><%= @followee.nickname %></p> <p><%= @followee.nickname %></p>
<img height="128" width="128" src="<%= avatar_url(@followee) %>"> <img height="128" width="128" src="<%= avatar_url(@followee) %>">
<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %> <%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "mfa"], fn f -> %>
<%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %> <%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
<br> <br>
<%= hidden_input f, :id, value: @followee.id %> <%= hidden_input f, :id, value: @followee.id %>

View file

@ -2,7 +2,7 @@
<h2><%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %></h2> <h2><%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %></h2>
<% else %> <% else %>
<h2><%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %></h2> <h2><%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %></h2>
<%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "status"], fn f -> %> <%= form_for @conn, Routes.remote_interaction_path(@conn, :remote_subscribe), [as: "status"], fn f -> %>
<%= hidden_input f, :status_id, value: @status_id %> <%= hidden_input f, :status_id, value: @status_id %>
<%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
<%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %> <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %>

View file

@ -2,7 +2,7 @@
<h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2> <h2><%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %></h2>
<% else %> <% else %>
<h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %></h2> <h2><%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %></h2>
<%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> <%= form_for @conn, Routes.remote_interaction_path(@conn, :remote_subscribe), [as: "user"], fn f -> %>
<%= hidden_input f, :nickname, value: @nickname %> <%= hidden_input f, :nickname, value: @nickname %>
<%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %>
<%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %>

View file

@ -2,7 +2,7 @@
<h1><%= link instance_name(), to: "/" %></h1> <h1><%= link instance_name(), to: "/" %></h1>
<h3> <h3>
<form class="pull-right collapse" method="POST" action="<%= Helpers.util_path(@conn, :remote_subscribe) %>"> <form class="pull-right collapse" method="POST" action="<%= Helpers.remote_interaction_path(@conn, :remote_subscribe) %>">
<input type="hidden" name="nickname" value="<%= @user.nickname %>"> <input type="hidden" name="nickname" value="<%= @user.nickname %>">
<input type="hidden" name="profile" value=""> <input type="hidden" name="profile" value="">
<button type="submit" class="collapse"><%= Gettext.dpgettext("static_pages", "static fe profile page remote follow button", "Remote follow") %></button> <button type="submit" class="collapse"><%= Gettext.dpgettext("static_pages", "static fe profile page remote follow button", "Remote follow") %></button>

View file

@ -1,31 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.UtilView do
use Pleroma.Web, :view
import Phoenix.HTML
import Phoenix.HTML.Form
import Phoenix.HTML.Link
alias Pleroma.Config
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Gettext
def status_net_config(instance) do
"""
<config>
<site>
<name>#{Keyword.get(instance, :name)}</name>
<site>#{Endpoint.url()}</site>
<textlimit>#{Keyword.get(instance, :limit)}</textlimit>
<closed>#{!Keyword.get(instance, :registrations_open)}</closed>
</site>
</config>
"""
end
def render("frontend_configurations.json", _) do
Config.get(:frontend_configurations, %{})
|> Enum.into(%{})
end
end

View file

@ -23,10 +23,12 @@ defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do
|> Pleroma.Repo.stream() |> Pleroma.Repo.stream()
|> Stream.each(fn expiration -> |> Stream.each(fn expiration ->
with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do
Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ Pleroma.Workers.PurgeExpiredActivity.enqueue(
activity_id: FlakeId.to_string(expiration.activity_id), %{
expires_at: expires_at activity_id: FlakeId.to_string(expiration.activity_id)
}) },
scheduled_at: expires_at
)
end end
end) end)
|> Stream.run() |> Stream.run()

View file

@ -21,7 +21,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now())
|> Pleroma.Repo.stream() |> Pleroma.Repo.stream()
|> Stream.each(fn token -> |> Stream.each(fn token ->
Pleroma.Workers.PurgeExpiredToken.enqueue(%{ enqueue(%{
token_id: token.id, token_id: token.id,
valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
mod: Pleroma.Web.OAuth.Token mod: Pleroma.Web.OAuth.Token
@ -33,7 +33,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now())
|> Pleroma.Repo.stream() |> Pleroma.Repo.stream()
|> Stream.each(fn token -> |> Stream.each(fn token ->
Pleroma.Workers.PurgeExpiredToken.enqueue(%{ enqueue(%{
token_id: token.id, token_id: token.id,
valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
mod: Pleroma.MFA.Token mod: Pleroma.MFA.Token
@ -41,4 +41,14 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do
end) end)
|> Stream.run() |> Stream.run()
end end
@spec enqueue(%{token_id: integer(), valid_until: DateTime.t()}) ::
{:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()}
defp enqueue(args) do
{scheduled_at, args} = Map.pop(args, :valid_until)
args
|> Pleroma.Workers.PurgeExpiredToken.new(scheduled_at: scheduled_at)
|> Oban.insert()
end
end end

View file

@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.AddExclusiveToLists do
use Ecto.Migration
def change do
alter table(:lists) do
add(:exclusive, :boolean, default: false)
end
end
end

File diff suppressed because one or more lines are too long

View file

@ -174,6 +174,22 @@ defmodule Pleroma.ConfigDBTest do
assert updated1.value == [groups: [c: 3, d: 4], key: [a: 1, b: 2]] assert updated1.value == [groups: [c: 3, d: 4], key: [a: 1, b: 2]]
assert updated2.value == [mascots: [c: 3, d: 4], key: [a: 1, b: 2]] assert updated2.value == [mascots: [c: 3, d: 4], key: [a: 1, b: 2]]
end end
test "rejects invalid :rate_limit values (e.g. empty-string scale from AdminFE)" do
assert {:error, _changeset} =
ConfigDB.update_or_create(%{
group: ":pleroma",
key: ":rate_limit",
value: [
%{
"tuple" => [
":statuses_actions",
[%{"tuple" => ["", 0]}, %{"tuple" => ["", ""]}]
]
}
]
})
end
end end
describe "delete/1" do describe "delete/1" do
@ -273,24 +289,28 @@ defmodule Pleroma.ConfigDBTest do
end end
test "sigil" do test "sigil" do
assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/ assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]").source ==
~r/comp[lL][aA][iI][nN]er/.source
end end
test "link sigil" do test "link sigil" do
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/ assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/").source ==
~r/https:\/\/example.com/.source
end end
test "link sigil with um modifiers" do test "link sigil with um modifiers" do
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um") == assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um").source ==
~r/https:\/\/example.com/um ~r/https:\/\/example.com/um.source
end end
test "link sigil with i modifier" do test "link sigil with i modifier" do
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i").source ==
~r/https:\/\/example.com/i.source
end end
test "link sigil with s modifier" do test "link sigil with s modifier" do
assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s").source ==
~r/https:\/\/example.com/s.source
end end
test "raise if valid delimiter not found" do test "raise if valid delimiter not found" do
@ -460,11 +480,11 @@ defmodule Pleroma.ConfigDBTest do
test "complex keyword with sigil" do test "complex keyword with sigil" do
assert ConfigDB.to_elixir_types([ assert ConfigDB.to_elixir_types([
%{"tuple" => [":federated_timeline_removal", []]}, %{"tuple" => [":federated_timeline_removal", []]},
%{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]}, %{"tuple" => [":reject", [~r/comp[lL][aA][iI][nN]er/.source]]},
%{"tuple" => [":replace", []]} %{"tuple" => [":replace", []]}
]) == [ ]) == [
federated_timeline_removal: [], federated_timeline_removal: [],
reject: [~r/comp[lL][aA][iI][nN]er/], reject: [~r/comp[lL][aA][iI][nN]er/.source],
replace: [] replace: []
] ]
end end

View file

@ -88,6 +88,49 @@ defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do
Pleroma.ReverseProxy.Client.Hackney.close(ref) Pleroma.ReverseProxy.Client.Hackney.close(ref)
end end
test "hackney reverse proxy follows nested redirects via proxy", %{
tls_server: tls_server,
proxy: proxy
} do
url = "#{tls_server.base_url}/nested_redirect"
opts = [
pool: :media,
proxy: proxy.proxy_url,
insecure: true,
connect_timeout: 1_000,
recv_timeout: 1_000,
follow_redirect: true
]
assert {:ok, 200, _headers, ref} =
Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts)
assert collect_body(ref) == "ok"
Pleroma.ReverseProxy.Client.Hackney.close(ref)
end
test "hackney reverse proxy stops following redirects after limit is reached", %{
tls_server: tls_server,
proxy: proxy
} do
url = "#{tls_server.base_url}/infinite_redirect"
opts = [
pool: :media,
proxy: proxy.proxy_url,
insecure: true,
connect_timeout: 1_000,
recv_timeout: 1_000,
follow_redirect: true
]
assert {:ok, 302, _headers, ref} =
Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts)
Pleroma.ReverseProxy.Client.Hackney.close(ref)
end
defp collect_body(ref, acc \\ "") do defp collect_body(ref, acc \\ "") do
case Pleroma.ReverseProxy.Client.Hackney.stream_body(ref) do case Pleroma.ReverseProxy.Client.Hackney.stream_body(ref) do
:done -> acc :done -> acc
@ -148,6 +191,12 @@ defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do
{:ok, data} <- recv_ssl_headers(ssl_socket), {:ok, data} <- recv_ssl_headers(ssl_socket),
{:ok, path} <- parse_path(data) do {:ok, path} <- parse_path(data) do
case path do case path do
"/infinite_redirect" ->
send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/infinite_redirect"}], "")
"/nested_redirect" ->
send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/redirect"}], "")
"/redirect" -> "/redirect" ->
send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/final"}], "") send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/final"}], "")

View file

@ -363,7 +363,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do
test "accepts the 'list' stream", %{token: token, user: user} do test "accepts the 'list' stream", %{token: token, user: user} do
posting_user = insert(:user) posting_user = insert(:user)
{:ok, list} = Pleroma.List.create("test", user) {:ok, list} = Pleroma.List.create(%{title: "test"}, user)
Pleroma.List.follow(list, posting_user) Pleroma.List.follow(list, posting_user)
assert {:ok, _} = start_socket("?stream=list&access_token=#{token.token}&list=#{list.id}") assert {:ok, _} = start_socket("?stream=list&access_token=#{token.token}&list=#{list.id}")

View file

@ -10,22 +10,23 @@ defmodule Pleroma.ListTest do
test "creating a list" do test "creating a list" do
user = insert(:user) user = insert(:user)
{:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user)
%Pleroma.List{title: title} = Pleroma.List.get(list.id, user) %Pleroma.List{title: title, exclusive: exclusive} = Pleroma.List.get(list.id, user)
assert title == "title" assert title == "title"
assert exclusive == false
end end
test "validates title" do test "validates title" do
user = insert(:user) user = insert(:user)
assert {:error, changeset} = Pleroma.List.create("", user) assert {:error, changeset} = Pleroma.List.create(%{title: ""}, user)
assert changeset.errors == [title: {"can't be blank", [validation: :required]}] assert changeset.errors == [title: {"can't be blank", [validation: :required]}]
end end
test "getting a list not belonging to the user" do test "getting a list not belonging to the user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user)
ret = Pleroma.List.get(list.id, other_user) ret = Pleroma.List.get(list.id, other_user)
assert is_nil(ret) assert is_nil(ret)
end end
@ -33,7 +34,7 @@ defmodule Pleroma.ListTest do
test "adding an user to a list" do test "adding an user to a list" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, list} = Pleroma.List.create("title", user) {:ok, list} = Pleroma.List.create(%{title: "title"}, user)
{:ok, %{following: following}} = Pleroma.List.follow(list, other_user) {:ok, %{following: following}} = Pleroma.List.follow(list, other_user)
assert [other_user.follower_address] == following assert [other_user.follower_address] == following
end end
@ -41,7 +42,7 @@ defmodule Pleroma.ListTest do
test "removing an user from a list" do test "removing an user from a list" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, list} = Pleroma.List.create("title", user) {:ok, list} = Pleroma.List.create(%{title: "title"}, user)
{:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
{:ok, %{following: following}} = Pleroma.List.unfollow(list, other_user) {:ok, %{following: following}} = Pleroma.List.unfollow(list, other_user)
assert [] == following assert [] == following
@ -49,14 +50,27 @@ defmodule Pleroma.ListTest do
test "renaming a list" do test "renaming a list" do
user = insert(:user) user = insert(:user)
{:ok, list} = Pleroma.List.create("title", user) {:ok, list} = Pleroma.List.create(%{title: "title"}, user)
{:ok, %{title: title}} = Pleroma.List.rename(list, "new") {:ok, %{title: title}} = Pleroma.List.update(list, %{title: "new"})
assert "new" == title assert "new" == title
end end
test "updating a list exclusivity" do
user = insert(:user)
{:ok, %{exclusive: exclusive} = list} =
Pleroma.List.create(%{title: "title", exclusive: true}, user)
assert exclusive == true
{:ok, %{exclusive: exclusive} = list} = Pleroma.List.update(list, %{exclusive: false})
assert exclusive == false
{:ok, %{exclusive: exclusive}} = Pleroma.List.update(list, %{exclusive: true})
assert exclusive == true
end
test "deleting a list" do test "deleting a list" do
user = insert(:user) user = insert(:user)
{:ok, list} = Pleroma.List.create("title", user) {:ok, list} = Pleroma.List.create(%{title: "title"}, user)
{:ok, list} = Pleroma.List.delete(list) {:ok, list} = Pleroma.List.delete(list)
assert is_nil(Repo.get(Pleroma.List, list.id)) assert is_nil(Repo.get(Pleroma.List, list.id))
end end
@ -65,7 +79,7 @@ defmodule Pleroma.ListTest do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
third_user = insert(:user) third_user = insert(:user)
{:ok, list} = Pleroma.List.create("title", user) {:ok, list} = Pleroma.List.create(%{title: "title"}, user)
{:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, other_user)
{:ok, list} = Pleroma.List.follow(list, third_user) {:ok, list} = Pleroma.List.follow(list, third_user)
{:ok, following} = Pleroma.List.get_following(list) {:ok, following} = Pleroma.List.get_following(list)
@ -76,9 +90,9 @@ defmodule Pleroma.ListTest do
test "getting all lists by an user" do test "getting all lists by an user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, list_one} = Pleroma.List.create("title", user) {:ok, list_one} = Pleroma.List.create(%{title: "title"}, user)
{:ok, list_two} = Pleroma.List.create("other title", user) {:ok, list_two} = Pleroma.List.create(%{title: "other title"}, user)
{:ok, list_three} = Pleroma.List.create("third title", other_user) {:ok, list_three} = Pleroma.List.create(%{title: "third title"}, other_user)
lists = Pleroma.List.for_user(user, %{}) lists = Pleroma.List.for_user(user, %{})
assert list_one in lists assert list_one in lists
assert list_two in lists assert list_two in lists
@ -88,9 +102,9 @@ defmodule Pleroma.ListTest do
test "getting all lists the user is a member of" do test "getting all lists the user is a member of" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, list_one} = Pleroma.List.create("title", user) {:ok, list_one} = Pleroma.List.create(%{title: "title"}, user)
{:ok, list_two} = Pleroma.List.create("other title", user) {:ok, list_two} = Pleroma.List.create(%{title: "other title"}, user)
{:ok, list_three} = Pleroma.List.create("third title", other_user) {:ok, list_three} = Pleroma.List.create(%{title: "third title"}, other_user)
{:ok, list_one} = Pleroma.List.follow(list_one, other_user) {:ok, list_one} = Pleroma.List.follow(list_one, other_user)
{:ok, list_two} = Pleroma.List.follow(list_two, other_user) {:ok, list_two} = Pleroma.List.follow(list_two, other_user)
{:ok, list_three} = Pleroma.List.follow(list_three, user) {:ok, list_three} = Pleroma.List.follow(list_three, user)
@ -106,8 +120,8 @@ defmodule Pleroma.ListTest do
not_owner = insert(:user) not_owner = insert(:user)
member_1 = insert(:user) member_1 = insert(:user)
member_2 = insert(:user) member_2 = insert(:user)
{:ok, owned_list} = Pleroma.List.create("owned", owner) {:ok, owned_list} = Pleroma.List.create(%{title: "owned"}, owner)
{:ok, not_owned_list} = Pleroma.List.create("not owned", not_owner) {:ok, not_owned_list} = Pleroma.List.create(%{title: "not owned"}, not_owner)
{:ok, owned_list} = Pleroma.List.follow(owned_list, member_1) {:ok, owned_list} = Pleroma.List.follow(owned_list, member_1)
{:ok, owned_list} = Pleroma.List.follow(owned_list, member_2) {:ok, owned_list} = Pleroma.List.follow(owned_list, member_2)
{:ok, not_owned_list} = Pleroma.List.follow(not_owned_list, member_1) {:ok, not_owned_list} = Pleroma.List.follow(not_owned_list, member_1)
@ -123,14 +137,14 @@ defmodule Pleroma.ListTest do
test "get by ap_id" do test "get by ap_id" do
user = insert(:user) user = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create(%{title: "foo"}, user)
assert Pleroma.List.get_by_ap_id(list.ap_id) == list assert Pleroma.List.get_by_ap_id(list.ap_id) == list
end end
test "memberships" do test "memberships" do
user = insert(:user) user = insert(:user)
member = insert(:user) member = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create(%{title: "foo"}, user)
{:ok, list} = Pleroma.List.follow(list, member) {:ok, list} = Pleroma.List.follow(list, member)
assert Pleroma.List.memberships(member) == [list.ap_id] assert Pleroma.List.memberships(member) == [list.ap_id]
@ -140,7 +154,7 @@ defmodule Pleroma.ListTest do
user = insert(:user) user = insert(:user)
member = insert(:user) member = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create(%{title: "foo"}, user)
{:ok, list} = Pleroma.List.follow(list, member) {:ok, list} = Pleroma.List.follow(list, member)
assert Pleroma.List.member?(list, member) assert Pleroma.List.member?(list, member)

View file

@ -36,11 +36,12 @@ defmodule Pleroma.MarkerTest do
insert(:notification, user: user, activity: insert(:note_activity)) insert(:notification, user: user, activity: insert(:note_activity))
insert(:notification, user: user, activity: insert(:note_activity)) insert(:notification, user: user, activity: insert(:note_activity))
insert(:marker, timeline: "home", user: user) insert(:marker, timeline: "home", user: user)
%Marker{} = refreshed_marker = refresh_record(marker)
assert Marker.get_markers( assert Marker.get_markers(
user, user,
["notifications"] ["notifications"]
) == [%Marker{refresh_record(marker) | unread_count: 2}] ) == [%{refreshed_marker | unread_count: 2}]
end end
end end

View file

@ -24,7 +24,8 @@ defmodule Pleroma.RepoTest do
describe "get_assoc/2" do describe "get_assoc/2" do
test "get assoc from preloaded data" do test "get assoc from preloaded data" do
user = %User{name: "Agent Smith"} user = %User{name: "Agent Smith"}
token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user} %Pleroma.Web.OAuth.Token{} = token = insert(:oauth_token)
token = %{token | user: user}
assert Repo.get_assoc(token, :user) == {:ok, user} assert Repo.get_assoc(token, :user) == {:ok, user}
end end

View file

@ -74,29 +74,6 @@ defmodule Pleroma.Search.MeilisearchTest do
assert_received("posted_to_meilisearch") assert_received("posted_to_meilisearch")
end end
test "doesn't index posts that are not public" do
user = insert(:user)
Enum.each(["private", "direct"], fn visibility ->
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: visibility
})
args = %{"op" => "add_to_index", "activity" => activity.id}
Config
|> expect(:get, fn
[Pleroma.Search, :module], nil ->
Meilisearch
end)
assert_enqueued(worker: SearchIndexingWorker, args: args)
assert :ok = perform_job(SearchIndexingWorker, args)
end)
end
test "deletes posts from index when deleted locally" do test "deletes posts from index when deleted locally" do
user = insert(:user) user = insert(:user)

View file

@ -0,0 +1,69 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.SearchTest do
use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
alias Pleroma.Web.CommonAPI
alias Pleroma.Workers.SearchIndexingWorker
test "indexes posts that are public" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "Well this is a story all about how my life got flipped turned upside down",
visibility: "public"
})
args = %{"op" => "add_to_index", "activity" => activity.id}
assert_enqueued(worker: SearchIndexingWorker, args: args)
end
test "doesn't index posts that are not public" do
user = insert(:user)
Enum.each(["private", "direct"], fn visibility ->
{:ok, activity} =
CommonAPI.post(user, %{
status: "guys i just don't wanna leave the swamp",
visibility: visibility
})
args = %{"op" => "add_to_index", "activity" => activity.id}
refute_enqueued(worker: SearchIndexingWorker, args: args)
end)
end
test "Indexes appropriate activity types" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
status: "I'm my own hype man",
visibility: "public"
})
args = %{"op" => "add_to_index", "activity" => activity.id}
assert_enqueued(worker: SearchIndexingWorker, args: args)
{:ok, fav_activity} = CommonAPI.favorite(activity.id, user)
args = %{"op" => "add_to_index", "activity" => fav_activity.id}
refute_enqueued(worker: SearchIndexingWorker, args: args)
{:ok, repeat_activity} = CommonAPI.repeat(activity.id, user)
args = %{"op" => "add_to_index", "activity" => repeat_activity.id}
refute_enqueued(worker: SearchIndexingWorker, args: args)
end
end

View file

@ -499,6 +499,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
"https://queef.in/storage/banner.gif" "https://queef.in/storage/banner.gif"
end end
test "works with alsoKnownAs as string" do
user_id = "https://hub.netzgemeinde.eu/channel/jupiter_rowland"
user_data =
"test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json"
|> File.read!()
user_data_decoded =
user_data
|> Jason.decode!()
Tesla.Mock.mock(fn
%{
method: :get,
url: ^user_id
} ->
%Tesla.Env{
status: 200,
body: user_data,
headers: [{"content-type", "application/activity+json"}]
}
end)
{:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
assert is_list(user.also_known_as)
assert user.also_known_as == [user_data_decoded["alsoKnownAs"]]
end
test "it fetches the appropriate tag-restricted posts" do test "it fetches the appropriate tag-restricted posts" do
user = insert(:user) user = insert(:user)
@ -1495,8 +1524,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
%{test_file: test_file} %{test_file: test_file}
end end
test "strips / from filename", %{test_file: file} do test "strips / from filename", %{test_file: %Plug.Upload{} = file} do
file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"} file = %{file | filename: "../../../../../nested/bad.jpg"}
{:ok, %Object{} = object} = ActivityPub.upload(file) {:ok, %Object{} = object} = ActivityPub.upload(file)
[%{"href" => href}] = object.data["url"] [%{"href" => href}] = object.data["url"]
assert Regex.match?(~r"/bad.jpg$", href) assert Regex.match?(~r"/bad.jpg$", href)
@ -1754,13 +1783,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
test "fetch_activities/2 returns activities addressed to a list " do test "fetch_activities/2 returns activities addressed to a list " do
user = insert(:user) user = insert(:user)
member = insert(:user) member = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create(%{title: "foo"}, user)
{:ok, list} = Pleroma.List.follow(list, member) {:ok, list} = Pleroma.List.follow(list, member)
{:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) {:ok, %Activity{} = activity} =
CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
activity = Repo.preload(activity, :bookmark) activity = Repo.preload(activity, :bookmark)
activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} activity = %{activity | thread_muted?: !!activity.thread_muted?}
assert ActivityPub.fetch_activities([], %{user: user}) == [activity] assert ActivityPub.fetch_activities([], %{user: user}) == [activity]
end end
@ -1960,7 +1990,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert User.following?(follower, old_user) assert User.following?(follower, old_user)
assert User.following?(follower_move_opted_out, old_user) assert User.following?(follower_move_opted_out, old_user)
assert {:ok, activity} = ActivityPub.move(old_user, new_user) assert {:ok, %Activity{} = activity} = ActivityPub.move(old_user, new_user)
assert %Activity{ assert %Activity{
actor: ^old_ap_id, actor: ^old_ap_id,
@ -1992,7 +2022,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert User.following?(follower_move_opted_out, old_user) assert User.following?(follower_move_opted_out, old_user)
refute User.following?(follower_move_opted_out, new_user) refute User.following?(follower_move_opted_out, new_user)
activity = %Activity{activity | object: nil} activity = %{activity | object: nil}
assert [%Notification{activity: ^activity}] = Notification.for_user(follower) assert [%Notification{activity: ^activity}] = Notification.for_user(follower)

View file

@ -10,18 +10,25 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
defp regexes_match!([],[]), do: true
defp regexes_match!([authority | authority_rest], [checked | checked_rest]) do
authority.source == checked.source and regexes_match!(authority_rest, checked_rest)
end
defp regexes_match!(_, _), do: false
test "subdomains_regex/1" do test "subdomains_regex/1" do
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ regexes = MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"])
~r/^unsafe.tld$/i,
~r/^(.*\.)*unsafe.tld$/i assert regexes_match!(regexes, [~r/^unsafe.tld$/i, ~r/^(.*\.)*unsafe.tld$/i])
]
end end
describe "subdomain_match/2" do describe "subdomain_match/2" do
test "common domains" do test "common domains" do
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i] assert regexes_match!(regexes, [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i])
assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "unsafe2.tld") assert MRF.subdomain_match?(regexes, "unsafe2.tld")
@ -32,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
test "wildcard domains with one subdomain" do test "wildcard domains with one subdomain" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"]) regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/i] assert regexes_match!(regexes, [~r/^(.*\.)*unsafe.tld$/i])
assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
@ -43,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
test "wildcard domains with two subdomains" do test "wildcard domains with two subdomains" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"]) regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/i] assert regexes_match!(regexes, [~r/^(.*\.)*unsafe.tld$/i])
assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
@ -54,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
test "matches are case-insensitive" do test "matches are case-insensitive" do
regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"]) regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"])
assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i] assert regexes_match!(regexes, [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i])
assert MRF.subdomain_match?(regexes, "UNSAFE.TLD") assert MRF.subdomain_match?(regexes, "UNSAFE.TLD")
assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD") assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD")

View file

@ -86,23 +86,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidationTest do
object = Object.normalize(post_activity, fetch: false) object = Object.normalize(post_activity, fetch: false)
# Another user can't announce it # Another user can't announce it
{:ok, announce, []} = Builder.announce(announcer, object, public: false) {:ok, announce, []} = Builder.announce(announcer, object, visibility: "private")
{:error, cng} = ObjectValidator.validate(announce, []) {:error, cng} = ObjectValidator.validate(announce, [])
assert {:actor, {"can not announce this object", []}} in cng.errors assert {:actor, {"can not announce this object", []}} in cng.errors
# The actor of the object can announce it # The actor of the object can announce it with a restrictive scope
{:ok, announce, []} = Builder.announce(user, object, public: false) {:ok, announce, []} = Builder.announce(user, object, visibility: "private")
assert {:ok, _, _} = ObjectValidator.validate(announce, [])
{:ok, announce, []} = Builder.announce(user, object, visibility: "direct")
assert {:ok, _, _} = ObjectValidator.validate(announce, []) assert {:ok, _, _} = ObjectValidator.validate(announce, [])
# The actor of the object can not announce it publicly # The actor of the object can not announce it publicly
{:ok, announce, []} = Builder.announce(user, object, public: true) {:ok, announce, []} = Builder.announce(user, object, visibility: "public")
{:error, cng1} = ObjectValidator.validate(announce, [])
{:error, cng} = ObjectValidator.validate(announce, []) {:ok, announce, []} = Builder.announce(user, object, visibility: "unlisted")
{:error, cng2} = ObjectValidator.validate(announce, [])
{:ok, announce, []} = Builder.announce(user, object, visibility: "local")
{:error, cng3} = ObjectValidator.validate(announce, [])
for cng <- [cng1, cng2, cng3] do
assert {:actor, {"can not announce this object publicly", []}} in cng.errors assert {:actor, {"can not announce this object publicly", []}} in cng.errors
end end
end end
end
end end

View file

@ -334,7 +334,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
test "activity with BCC is published to a list member." do test "activity with BCC is published to a list member." do
actor = insert(:user) actor = insert(:user)
{:ok, list} = Pleroma.List.create("list", actor) {:ok, list} = Pleroma.List.create(%{title: "list"}, actor)
list_member = insert(:user, %{local: false}) list_member = insert(:user, %{local: false})
Pleroma.List.follow(list, list_member) Pleroma.List.follow(list, list_member)

View file

@ -784,13 +784,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
{:ok, post} = CommonAPI.post(poster, %{status: "hey"}) {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
{:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"}) {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
{:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true) {:ok, announce_data, _meta} = Builder.announce(user, post.object, visibility: "public")
{:ok, private_announce_data, _meta} = {:ok, private_announce_data, _meta} =
Builder.announce(user, private_post.object, public: false) Builder.announce(user, private_post.object, visibility: "private")
{:ok, relay_announce_data, _meta} = {:ok, relay_announce_data, _meta} =
Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true) Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object,
visibility: "public"
)
{:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true) {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
{:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true) {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true)

View file

@ -581,7 +581,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
test "it strips BCC field" do test "it strips BCC field" do
user = insert(:user) user = insert(:user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create(%{title: "foo"}, user)
{:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do
following = insert(:user) following = insert(:user)
unrelated = insert(:user) unrelated = insert(:user)
{:ok, following, user} = Pleroma.User.follow(following, user) {:ok, following, user} = Pleroma.User.follow(following, user)
{:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.create(%{title: "foo"}, user)
Pleroma.List.follow(list, unrelated) Pleroma.List.follow(list, unrelated)

Some files were not shown because too many files have changed in this diff Show more