mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2025-04-24 05:47:41 -04:00
Merge remote-tracking branch 'origin/develop' into user-exports-api
This commit is contained in:
commit
ab6d15dcc0
28 changed files with 253 additions and 184 deletions
0
changelog.d/dialyzer.skip
Normal file
0
changelog.d/dialyzer.skip
Normal file
1
changelog.d/following-state.fix
Normal file
1
changelog.d/following-state.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Resolved edge case where the API can report you are following a user but the relationship is not fully established.
|
1
changelog.d/oauth-app-spam.fix
Normal file
1
changelog.d/oauth-app-spam.fix
Normal file
|
@ -0,0 +1 @@
|
|||
Add a rate limiter to the OAuth App creation endpoint and ensure registered apps are assigned to users.
|
|
@ -1 +0,0 @@
|
|||
Prevent OAuth App flow from creating duplicate entries
|
1
changelog.d/oban-uniques.change
Normal file
1
changelog.d/oban-uniques.change
Normal file
|
@ -0,0 +1 @@
|
|||
Adjust more Oban workers to enforce unique job constraints.
|
|
@ -597,7 +597,8 @@ config :pleroma, Oban,
|
|||
plugins: [{Oban.Plugins.Pruner, max_age: 900}],
|
||||
crontab: [
|
||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
|
||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
|
||||
{"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}
|
||||
]
|
||||
|
||||
config :pleroma, Pleroma.Formatter,
|
||||
|
@ -711,6 +712,7 @@ config :pleroma, :rate_limit,
|
|||
timeline: {500, 3},
|
||||
search: [{1000, 10}, {1000, 30}],
|
||||
app_account_creation: {1_800_000, 25},
|
||||
oauth_app_creation: {900_000, 5},
|
||||
relations_actions: {10_000, 10},
|
||||
relation_id_action: {60_000, 2},
|
||||
statuses_actions: {10_000, 15},
|
||||
|
|
|
@ -58,8 +58,12 @@ defmodule Pleroma.Object.Fetcher do
|
|||
end
|
||||
end
|
||||
|
||||
@typep fetcher_errors ::
|
||||
:error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier
|
||||
|
||||
# Note: will create a Create activity, which we need internally at the moment.
|
||||
@spec fetch_object_from_id(String.t(), list()) :: {:ok, Object.t()} | {:error | :reject, any()}
|
||||
@spec fetch_object_from_id(String.t(), list()) ::
|
||||
{:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors()
|
||||
def fetch_object_from_id(id, options \\ []) do
|
||||
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
||||
|
|
|
@ -94,9 +94,6 @@ defmodule Pleroma.User.Backup do
|
|||
else
|
||||
true ->
|
||||
{:error, "Backup is missing id. Please insert it into the Repo first."}
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -123,14 +120,13 @@ defmodule Pleroma.User.Backup do
|
|||
end
|
||||
|
||||
defp permitted?(user) do
|
||||
with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)},
|
||||
days = Config.get([__MODULE__, :limit_days]),
|
||||
diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days),
|
||||
{_, true} <- {:diff, diff > days} do
|
||||
true
|
||||
with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)} do
|
||||
days = Config.get([__MODULE__, :limit_days])
|
||||
diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days)
|
||||
|
||||
diff > days
|
||||
else
|
||||
{:last, nil} -> true
|
||||
{:diff, false} -> false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -171,6 +167,7 @@ defmodule Pleroma.User.Backup do
|
|||
|
||||
@spec export(User.t(), export_types(), export_formats()) :: binary()
|
||||
def export(user, type, format \\ :csv)
|
||||
|
||||
def export(user, type, format) do
|
||||
type = Atom.to_string(type)
|
||||
mapping_fun = get_mapping_fun(type)
|
||||
|
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.User.Import do
|
|||
|
||||
require Logger
|
||||
|
||||
@spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()}
|
||||
@spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()}
|
||||
def perform(:mute_import, %User{} = user, actor) do
|
||||
with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
|
||||
{_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)},
|
||||
|
@ -49,7 +49,7 @@ defmodule Pleroma.User.Import do
|
|||
|
||||
defp handle_error(op, user_id, error) do
|
||||
Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}")
|
||||
error
|
||||
{:error, error}
|
||||
end
|
||||
|
||||
def blocks_import(%User{} = user, [_ | _] = actors) do
|
||||
|
|
|
@ -22,22 +22,27 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
|||
defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
|
||||
defp config, do: Config.get([:pipeline, :config], Config)
|
||||
|
||||
@spec common_pipeline(map(), keyword()) ::
|
||||
{:ok, Activity.t() | Object.t(), keyword()} | {:error | :reject, any()}
|
||||
@type results :: {:ok, Activity.t() | Object.t(), keyword()}
|
||||
@type errors :: {:error | :reject, any()}
|
||||
|
||||
# The Repo.transaction will wrap the result in an {:ok, _}
|
||||
# and only returns an {:error, _} if the error encountered was related
|
||||
# to the SQL transaction
|
||||
@spec common_pipeline(map(), keyword()) :: results() | errors()
|
||||
def common_pipeline(object, meta) do
|
||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
||||
{:ok, {:ok, activity, meta}} ->
|
||||
side_effects().handle_after_transaction(meta)
|
||||
{:ok, activity, meta}
|
||||
|
||||
{:ok, value} ->
|
||||
value
|
||||
{:ok, {:error, _} = error} ->
|
||||
error
|
||||
|
||||
{:ok, {:reject, _} = error} ->
|
||||
error
|
||||
|
||||
{:error, e} ->
|
||||
{:error, e}
|
||||
|
||||
{:reject, e} ->
|
||||
{:reject, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
require Pleroma.Constants
|
||||
require Logger
|
||||
|
||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def block(blocked, blocker) do
|
||||
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||
|
@ -35,7 +35,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec post_chat_message(User.t(), User.t(), String.t(), list()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
{:ok, Activity.t()} | Pipeline.errors()
|
||||
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||
:ok <- validate_chat_attachment_attribution(maybe_attachment, user),
|
||||
|
@ -58,7 +58,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
)} do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:common_pipeline, {:reject, _} = e} -> e
|
||||
{:common_pipeline, e} -> e
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
@ -99,7 +99,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unblock(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec unblock(User.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | {:ok, :no_activity} | Pipeline.errors() | {:error, :not_blocking}
|
||||
def unblock(blocked, blocker) do
|
||||
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
||||
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
||||
|
@ -120,7 +121,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec follow(User.t(), User.t()) ::
|
||||
{:ok, User.t(), User.t(), Activity.t() | Object.t()} | {:error, :rejected}
|
||||
{:ok, User.t(), User.t(), Activity.t() | Object.t()}
|
||||
| {:error, :rejected}
|
||||
| Pipeline.errors()
|
||||
def follow(followed, follower) do
|
||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||
|
||||
|
@ -145,7 +148,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()}
|
||||
@spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors()
|
||||
def accept_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
|
||||
|
@ -154,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} | nil
|
||||
@spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() | nil
|
||||
def reject_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
|
||||
|
@ -163,7 +166,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec delete(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec delete(String.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | Pipeline.errors() | {:error, :not_found | String.t()}
|
||||
def delete(activity_id, user) do
|
||||
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
||||
|
@ -213,7 +217,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, :not_found}
|
||||
def repeat(id, user, params \\ %{}) do
|
||||
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
||||
object = %Object{} <- Object.normalize(activity, fetch: false),
|
||||
|
@ -231,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, :not_found | String.t()}
|
||||
def unrepeat(id, user) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)},
|
||||
|
@ -247,7 +251,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec favorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec favorite(String.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | {:ok, :already_liked} | {:error, :not_found | String.t()}
|
||||
def favorite(id, %User{} = user) do
|
||||
case favorite_helper(user, id) do
|
||||
{:ok, _} = res ->
|
||||
|
@ -285,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unfavorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec unfavorite(String.t(), User.t()) ::
|
||||
{:ok, Activity.t()} | {:error, :not_found | String.t()}
|
||||
def unfavorite(id, user) do
|
||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||
{:find_activity, Activity.get_by_id(id)},
|
||||
|
@ -302,7 +308,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec react_with_emoji(String.t(), User.t(), String.t()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
{:ok, Activity.t()} | {:error, String.t()}
|
||||
def react_with_emoji(id, user, emoji) do
|
||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||
object <- Object.normalize(activity, fetch: false),
|
||||
|
@ -316,7 +322,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
|
||||
@spec unreact_with_emoji(String.t(), User.t(), String.t()) ::
|
||||
{:ok, Activity.t()} | {:error, any()}
|
||||
{:ok, Activity.t()} | {:error, String.t()}
|
||||
def unreact_with_emoji(id, user, emoji) do
|
||||
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
|
||||
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)},
|
||||
|
@ -329,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | {:error, any()}
|
||||
@spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | Pipeline.errors()
|
||||
def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do
|
||||
with :ok <- validate_not_author(object, user),
|
||||
:ok <- validate_existing_votes(user, object),
|
||||
|
@ -461,7 +467,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, nil}
|
||||
def update(orig_activity, %User{} = user, changes) do
|
||||
with orig_object <- Object.normalize(orig_activity),
|
||||
{:ok, new_object} <- make_update_data(user, orig_object, changes),
|
||||
|
@ -497,7 +503,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
||||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def pin(id, %User{} = user) do
|
||||
with %Activity{} = activity <- create_activity_by_id(id),
|
||||
true <- activity_belongs_to_actor(activity, user.ap_id),
|
||||
|
@ -537,7 +543,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||
def unpin(id, user) do
|
||||
with %Activity{} = activity <- create_activity_by_id(id),
|
||||
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
||||
|
@ -552,7 +558,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
@spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
@spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, String.t()}
|
||||
def add_mute(activity, user, params \\ %{}) do
|
||||
expires_in = Map.get(params, :expires_in, 0)
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
|
||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
plug(Pleroma.Web.Plugs.RateLimiter, [name: :oauth_app_creation] when action == :create)
|
||||
|
||||
plug(:skip_auth when action in [:create, :verify_credentials])
|
||||
|
||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||
|
@ -36,7 +38,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
|> Map.put(:scopes, scopes)
|
||||
|> Maps.put_if_present(:user_id, user_id)
|
||||
|
||||
with {:ok, app} <- App.get_or_make(app_attrs) do
|
||||
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||
{:ok, app} <- Repo.insert(cs) do
|
||||
render(conn, "show.json", app: app)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -92,14 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
User.get_follow_state(reading_user, target)
|
||||
end
|
||||
|
||||
followed_by =
|
||||
if following_relationships do
|
||||
case FollowingRelationship.find(following_relationships, target, reading_user) do
|
||||
%{state: :follow_accept} -> true
|
||||
_ -> false
|
||||
end
|
||||
else
|
||||
User.following?(target, reading_user)
|
||||
followed_by = FollowingRelationship.following?(target, reading_user)
|
||||
following = FollowingRelationship.following?(reading_user, target)
|
||||
|
||||
requested =
|
||||
cond do
|
||||
following -> false
|
||||
true -> match?(:follow_pending, follow_state)
|
||||
end
|
||||
|
||||
subscribing =
|
||||
|
@ -114,7 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
following: follow_state == :follow_accept,
|
||||
following: following,
|
||||
followed_by: followed_by,
|
||||
blocking:
|
||||
UserRelationship.exists?(
|
||||
|
@ -150,7 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
),
|
||||
subscribing: subscribing,
|
||||
notifying: subscribing,
|
||||
requested: follow_state == :follow_pending,
|
||||
requested: requested,
|
||||
domain_blocking: User.blocks_domain?(reading_user, target),
|
||||
showing_reblogs:
|
||||
not UserRelationship.exists?(
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
import Ecto.Query
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
|
@ -67,27 +68,35 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
|
||||
app
|
||||
|> changeset(params)
|
||||
|> validate_required([:scopes])
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets app by attrs or create new with attrs.
|
||||
Updates the attrs if needed.
|
||||
Gets app by attrs or create new with attrs.
|
||||
And updates the scopes if need.
|
||||
"""
|
||||
@spec get_or_make(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def get_or_make(attrs) do
|
||||
with %__MODULE__{} = app <- Repo.get_by(__MODULE__, client_name: attrs.client_name) do
|
||||
__MODULE__.update(app.id, Map.take(attrs, [:scopes, :website]))
|
||||
@spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
|
||||
def get_or_make(attrs, scopes) do
|
||||
with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do
|
||||
update_scopes(app, scopes)
|
||||
else
|
||||
_e ->
|
||||
%__MODULE__{}
|
||||
|> register_changeset(attrs)
|
||||
|> register_changeset(Map.put(attrs, :scopes, scopes))
|
||||
|> Repo.insert()
|
||||
end
|
||||
end
|
||||
|
||||
defp update_scopes(%__MODULE__{} = app, []), do: {:ok, app}
|
||||
defp update_scopes(%__MODULE__{scopes: scopes} = app, scopes), do: {:ok, app}
|
||||
|
||||
defp update_scopes(%__MODULE__{} = app, scopes) do
|
||||
app
|
||||
|> change(%{scopes: scopes})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec search(map()) :: {:ok, [t()], non_neg_integer()}
|
||||
def search(params) do
|
||||
query = from(a in __MODULE__)
|
||||
|
@ -147,4 +156,29 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
Map.put(acc, key, error)
|
||||
end)
|
||||
end
|
||||
|
||||
@spec maybe_update_owner(Token.t()) :: :ok
|
||||
def maybe_update_owner(%Token{app_id: app_id, user_id: user_id}) when not is_nil(user_id) do
|
||||
__MODULE__.update(app_id, %{user_id: user_id})
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def maybe_update_owner(_), do: :ok
|
||||
|
||||
@spec remove_orphans(pos_integer()) :: :ok
|
||||
def remove_orphans(limit \\ 100) do
|
||||
fifteen_mins_ago = DateTime.add(DateTime.utc_now(), -900, :second)
|
||||
|
||||
Repo.transaction(fn ->
|
||||
from(a in __MODULE__,
|
||||
where: is_nil(a.user_id) and a.inserted_at < ^fifteen_mins_ago,
|
||||
limit: ^limit
|
||||
)
|
||||
|> Repo.all()
|
||||
|> Enum.each(&Repo.delete(&1))
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -318,6 +318,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||
|
||||
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
|
||||
App.maybe_update_owner(token)
|
||||
|
||||
conn
|
||||
|> AuthHelper.put_session_token(token.token)
|
||||
|> json(OAuthView.render("token.json", view_params))
|
||||
|
|
21
lib/pleroma/workers/cron/app_cleanup_worker.ex
Normal file
21
lib/pleroma/workers/cron/app_cleanup_worker.ex
Normal file
|
@ -0,0 +1,21 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.Cron.AppCleanupWorker do
|
||||
@moduledoc """
|
||||
Cleans up registered apps that were never associated with a user.
|
||||
"""
|
||||
|
||||
use Oban.Worker, queue: "background"
|
||||
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
||||
@impl true
|
||||
def perform(_job) do
|
||||
App.remove_orphans()
|
||||
end
|
||||
|
||||
@impl true
|
||||
def timeout(_job), do: :timer.seconds(30)
|
||||
end
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
use Oban.Worker, queue: :federator_incoming, max_attempts: 5
|
||||
use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Workers.RemoteFetcherWorker do
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
||||
use Oban.Worker, queue: :background
|
||||
use Oban.Worker, queue: :background, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.RichMediaWorker do
|
|||
alias Pleroma.Web.RichMedia.Backfill
|
||||
alias Pleroma.Web.RichMedia.Card
|
||||
|
||||
use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: 300]
|
||||
use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Workers.UserRefreshWorker do
|
||||
use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: 300]
|
||||
use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity]
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Workers.WebPusherWorker do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.Push.Impl
|
||||
|
||||
use Oban.Worker, queue: :web_push
|
||||
use Oban.Worker, queue: :web_push, unique: [period: :infinity]
|
||||
|
||||
@impl true
|
||||
def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -22,7 +22,7 @@
|
|||
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
|
||||
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
|
||||
"credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"},
|
||||
"credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
|
||||
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
|
||||
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
|
||||
|
|
21
priv/repo/migrations/20240904142434_assign_app_user.exs
Normal file
21
priv/repo/migrations/20240904142434_assign_app_user.exs
Normal file
|
@ -0,0 +1,21 @@
|
|||
defmodule Pleroma.Repo.Migrations.AssignAppUser do
|
||||
use Ecto.Migration
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
def up do
|
||||
Repo.all(Token)
|
||||
|> Enum.group_by(fn x -> Map.get(x, :app_id) end)
|
||||
|> Enum.each(fn {_app_id, tokens} ->
|
||||
token =
|
||||
Enum.filter(tokens, fn x -> not is_nil(x.user_id) end)
|
||||
|> List.first()
|
||||
|
||||
App.maybe_update_owner(token)
|
||||
end)
|
||||
end
|
||||
|
||||
def down, do: :ok
|
||||
end
|
|
@ -41,6 +41,10 @@ defmodule Pleroma.HTMLTest do
|
|||
<span class="h-card"><a class="u-url mention animate-spin">@<span>foo</span></a></span>
|
||||
"""
|
||||
|
||||
@mention_hashtags_sample """
|
||||
<a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a>
|
||||
"""
|
||||
|
||||
describe "StripTags scrubber" do
|
||||
test "works as expected" do
|
||||
expected = """
|
||||
|
@ -126,6 +130,15 @@ defmodule Pleroma.HTMLTest do
|
|||
Pleroma.HTML.Scrubber.TwitterText
|
||||
)
|
||||
end
|
||||
|
||||
test "does allow mention hashtags" do
|
||||
expected = """
|
||||
<a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a>
|
||||
"""
|
||||
|
||||
assert expected ==
|
||||
HTML.filter_tags(@mention_hashtags_sample, Pleroma.HTML.Scrubber.Default)
|
||||
end
|
||||
end
|
||||
|
||||
describe "default scrubber" do
|
||||
|
@ -189,6 +202,15 @@ defmodule Pleroma.HTMLTest do
|
|||
Pleroma.HTML.Scrubber.Default
|
||||
)
|
||||
end
|
||||
|
||||
test "does allow mention hashtags" do
|
||||
expected = """
|
||||
<a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a>
|
||||
"""
|
||||
|
||||
assert expected ==
|
||||
HTML.filter_tags(@mention_hashtags_sample, Pleroma.HTML.Scrubber.Default)
|
||||
end
|
||||
end
|
||||
|
||||
describe "extract_first_external_url_from_object" do
|
||||
|
|
|
@ -89,114 +89,4 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
|
|||
assert expected == json_response_and_validate_schema(conn, 200)
|
||||
assert app.user_id == user.id
|
||||
end
|
||||
|
||||
test "creates an oauth app without a user", %{conn: conn} do
|
||||
app_attrs = build(:oauth_app)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: app_attrs.client_name,
|
||||
redirect_uris: app_attrs.redirect_uris
|
||||
})
|
||||
|
||||
[app] = Repo.all(App)
|
||||
|
||||
expected = %{
|
||||
"name" => app.client_name,
|
||||
"website" => app.website,
|
||||
"client_id" => app.client_id,
|
||||
"client_secret" => app.client_secret,
|
||||
"id" => app.id |> to_string(),
|
||||
"redirect_uri" => app.redirect_uris,
|
||||
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
|
||||
}
|
||||
|
||||
assert expected == json_response_and_validate_schema(conn, 200)
|
||||
end
|
||||
|
||||
test "does not duplicate apps with the same client name", %{conn: conn} do
|
||||
client_name = "BleromaSE"
|
||||
redirect_uris = "https://bleroma.app/oauth-callback"
|
||||
|
||||
for _i <- 1..3 do
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: client_name,
|
||||
redirect_uris: redirect_uris
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
end
|
||||
|
||||
apps = Repo.all(App)
|
||||
|
||||
assert length(apps) == 1
|
||||
assert List.first(apps).client_name == client_name
|
||||
assert List.first(apps).redirect_uris == redirect_uris
|
||||
end
|
||||
|
||||
test "app scopes can be updated", %{conn: conn} do
|
||||
client_name = "BleromaSE"
|
||||
redirect_uris = "https://bleroma.app/oauth-callback"
|
||||
website = "https://bleromase.com"
|
||||
scopes = "read write"
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: client_name,
|
||||
redirect_uris: redirect_uris,
|
||||
website: website,
|
||||
scopes: scopes
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert List.first(Repo.all(App)).scopes == String.split(scopes, " ")
|
||||
|
||||
updated_scopes = "read write push"
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: client_name,
|
||||
redirect_uris: redirect_uris,
|
||||
website: website,
|
||||
scopes: updated_scopes
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert List.first(Repo.all(App)).scopes == String.split(updated_scopes, " ")
|
||||
end
|
||||
|
||||
test "app website URL can be updated", %{conn: conn} do
|
||||
client_name = "BleromaSE"
|
||||
redirect_uris = "https://bleroma.app/oauth-callback"
|
||||
website = "https://bleromase.com"
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: client_name,
|
||||
redirect_uris: redirect_uris,
|
||||
website: website
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert List.first(Repo.all(App)).website == website
|
||||
|
||||
updated_website = "https://bleromase2ultimateedition.com"
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: client_name,
|
||||
redirect_uris: redirect_uris,
|
||||
website: updated_website
|
||||
})
|
||||
|> json_response_and_validate_schema(200)
|
||||
|
||||
assert List.first(Repo.all(App)).website == updated_website
|
||||
end
|
||||
end
|
||||
|
|
|
@ -456,6 +456,45 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
|
|||
test_relationship_rendering(user, other_user, expected)
|
||||
end
|
||||
|
||||
test "relationship does not indicate following if a FollowingRelationship is missing" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user, local: false)
|
||||
|
||||
# Create a follow relationship with the real Follow Activity and Accept it
|
||||
assert {:ok, _, _, _} = CommonAPI.follow(other_user, user)
|
||||
assert {:ok, _} = CommonAPI.accept_follow_request(user, other_user)
|
||||
|
||||
assert %{data: %{"state" => "accept"}} =
|
||||
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, other_user)
|
||||
|
||||
# Fetch the relationship and forcibly delete it to simulate
|
||||
# a Follow Accept that did not complete processing
|
||||
%{following_relationships: [relationship]} =
|
||||
Pleroma.UserRelationship.view_relationships_option(user, [other_user])
|
||||
|
||||
assert {:ok, _} = Pleroma.Repo.delete(relationship)
|
||||
|
||||
assert %{following_relationships: [], user_relationships: []} ==
|
||||
Pleroma.UserRelationship.view_relationships_option(user, [other_user])
|
||||
|
||||
expected =
|
||||
Map.merge(
|
||||
@blank_response,
|
||||
%{
|
||||
following: false,
|
||||
followed_by: false,
|
||||
muting: false,
|
||||
muting_notifications: false,
|
||||
subscribing: false,
|
||||
notifying: false,
|
||||
showing_reblogs: true,
|
||||
id: to_string(other_user.id)
|
||||
}
|
||||
)
|
||||
|
||||
test_relationship_rendering(user, other_user, expected)
|
||||
end
|
||||
|
||||
test "represent a relationship for the blocking and blocked user" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
|
|
@ -12,23 +12,20 @@ defmodule Pleroma.Web.OAuth.AppTest do
|
|||
test "gets exist app" do
|
||||
attrs = %{client_name: "Mastodon-Local", redirect_uris: "."}
|
||||
app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]}))
|
||||
{:ok, %App{} = exist_app} = App.get_or_make(attrs)
|
||||
{:ok, %App{} = exist_app} = App.get_or_make(attrs, [])
|
||||
assert exist_app == app
|
||||
end
|
||||
|
||||
test "make app" do
|
||||
attrs = %{client_name: "Mastodon-Local", redirect_uris: ".", scopes: ["write"]}
|
||||
{:ok, %App{} = app} = App.get_or_make(attrs)
|
||||
attrs = %{client_name: "Mastodon-Local", redirect_uris: "."}
|
||||
{:ok, %App{} = app} = App.get_or_make(attrs, ["write"])
|
||||
assert app.scopes == ["write"]
|
||||
end
|
||||
|
||||
test "gets exist app and updates scopes" do
|
||||
attrs = %{client_name: "Mastodon-Local", redirect_uris: ".", scopes: ["read", "write"]}
|
||||
app = insert(:oauth_app, attrs)
|
||||
|
||||
{:ok, %App{} = exist_app} =
|
||||
App.get_or_make(%{attrs | scopes: ["read", "write", "follow", "push"]})
|
||||
|
||||
attrs = %{client_name: "Mastodon-Local", redirect_uris: "."}
|
||||
app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]}))
|
||||
{:ok, %App{} = exist_app} = App.get_or_make(attrs, ["read", "write", "follow", "push"])
|
||||
assert exist_app.id == app.id
|
||||
assert exist_app.scopes == ["read", "write", "follow", "push"]
|
||||
end
|
||||
|
@ -56,4 +53,21 @@ defmodule Pleroma.Web.OAuth.AppTest do
|
|||
|
||||
assert Enum.sort(App.get_user_apps(user)) == Enum.sort(apps)
|
||||
end
|
||||
|
||||
test "removes orphaned apps" do
|
||||
attrs = %{client_name: "Mastodon-Local", redirect_uris: "."}
|
||||
{:ok, %App{} = old_app} = App.get_or_make(attrs, ["write"])
|
||||
|
||||
attrs = %{client_name: "PleromaFE", redirect_uris: "."}
|
||||
{:ok, %App{} = app} = App.get_or_make(attrs, ["write"])
|
||||
|
||||
# backdate the old app so it's within the threshold for being cleaned up
|
||||
{:ok, _} =
|
||||
"UPDATE apps SET inserted_at = now() - interval '1 hour' WHERE id = #{old_app.id}"
|
||||
|> Pleroma.Repo.query()
|
||||
|
||||
App.remove_orphans()
|
||||
|
||||
assert [app] == Pleroma.Repo.all(App)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|
|||
alias Pleroma.MFA.TOTP
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
@ -770,6 +771,9 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|
|||
|
||||
{:ok, auth} = Authorization.create_authorization(app, user, ["write"])
|
||||
|
||||
# Verify app has no associated user yet
|
||||
assert %Pleroma.Web.OAuth.App{user_id: nil} = Repo.get_by(App, %{id: app.id})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> post("/oauth/token", %{
|
||||
|
@ -786,6 +790,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do
|
|||
assert token
|
||||
assert token.scopes == auth.scopes
|
||||
assert user.ap_id == ap_id
|
||||
|
||||
# Verify app has an associated user now
|
||||
user_id = user.id
|
||||
assert %Pleroma.Web.OAuth.App{user_id: ^user_id} = Repo.get_by(App, %{id: app.id})
|
||||
end
|
||||
|
||||
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue