Merge remote-tracking branch 'remotes/origin/develop' into 1427-oauth-admin-scopes

This commit is contained in:
Ivan Tashkinov 2019-12-06 00:26:31 +03:00
commit 13926537b6
75 changed files with 1493 additions and 390 deletions

View File

@ -31,6 +31,7 @@ build:
benchmark:
stage: benchmark
when: manual
variables:
MIX_ENV: benchmark
services:
@ -55,6 +56,19 @@ unit-testing:
- mix ecto.migrate
- mix coveralls --preload-modules
federated-testing:
stage: test
services:
- name: minibikini/postgres-with-rum:12
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- mix deps.get
- mix ecto.create
- mix ecto.migrate
- epmd -daemon
- mix test --trace --only federated
unit-testing-rum:
stage: test
services:

View File

@ -42,6 +42,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Authentication: Added rate limit for password-authorized actions / login existence checks
- Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app.
- Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
- Mix task to list all users (`mix pleroma.user list`)
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
- MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
@ -66,10 +67,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `DELETE /api/pleroma/admin/users` (`nickname` query param or `nickname` sent in JSON body) is deprecated in favor of: `DELETE /api/pleroma/admin/users` (`nicknames` query array param or `nicknames` sent in JSON body)
- Admin API: Add `GET /api/pleroma/admin/relay` endpoint - lists all followed relays
- Pleroma API: `POST /api/v1/pleroma/conversations/read` to mark all conversations as read
- ActivityPub: Support `Move` activities
- Mastodon API: Add `/api/v1/markers` for managing timeline read markers
- Mastodon API: Add the `recipients` parameter to `GET /api/v1/conversations`
- Configuration: `feed` option for user atom feed.
- Pleroma API: Add Emoji reactions
- Admin API: Add `/api/pleroma/admin/instances/:instance/statuses` - lists all statuses from a given instance
- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
</details>
### Fixed
@ -80,6 +84,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
- Admin API: Error when trying to update reports in the "old" format
</details>
## [1.1.6] - 2019-11-19

View File

@ -95,7 +95,36 @@ defmodule Pleroma.LoadTesting.Fetcher do
for: user,
as: :activity
})
end
end,
"Rendering favorites timeline" => fn ->
conn = Phoenix.ConnTest.build_conn(:get, "http://localhost:4001/api/v1/favourites", nil)
Pleroma.Web.MastodonAPI.StatusController.favourites(
%Plug.Conn{conn |
assigns: %{user: user},
query_params: %{"limit" => "0"},
body_params: %{},
cookies: %{},
params: %{},
path_params: %{},
private: %{
Pleroma.Web.Router => {[], %{}},
phoenix_router: Pleroma.Web.Router,
phoenix_action: :favourites,
phoenix_controller: Pleroma.Web.MastodonAPI.StatusController,
phoenix_endpoint: Pleroma.Web.Endpoint,
phoenix_format: "json",
phoenix_layout: {Pleroma.Web.LayoutView, "app.html"},
phoenix_recycled: true,
phoenix_view: Pleroma.Web.MastodonAPI.StatusView,
plug_session: %{"user_id" => user.id},
plug_session_fetch: :done,
plug_session_info: :write,
plug_skip_csrf_protection: true
}
},
%{})
end,
})
end

View File

@ -2,6 +2,24 @@ defmodule Pleroma.LoadTesting.Generator do
use Pleroma.LoadTesting.Helper
alias Pleroma.Web.CommonAPI
def generate_like_activities(user, posts) do
count_likes = Kernel.trunc(length(posts) / 4)
IO.puts("Starting generating #{count_likes} like activities...")
{time, _} =
:timer.tc(fn ->
Task.async_stream(
Enum.take_random(posts, count_likes),
fn post -> {:ok, _, _} = CommonAPI.favorite(post.id, user) end,
max_concurrency: 10,
timeout: 30_000
)
|> Stream.run()
end)
IO.puts("Inserting like activities take #{to_sec(time)} sec.\n")
end
def generate_users(opts) do
IO.puts("Starting generating #{opts[:users_max]} users...")
{time, _} = :timer.tc(fn -> do_generate_users(opts) end)
@ -31,7 +49,6 @@ defmodule Pleroma.LoadTesting.Generator do
password_hash:
"$pbkdf2-sha512$160000$bU.OSFI7H/yqWb5DPEqyjw$uKp/2rmXw12QqnRRTqTtuk2DTwZfF8VR4MYW2xMeIlqPR/UX1nT1CEKVUx2CowFMZ5JON8aDvURrZpJjSgqXrg",
bio: "Tester Number #{i}",
info: %{},
local: remote
}

View File

@ -100,6 +100,10 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do
generate_remote_activities(user, remote_users)
generate_like_activities(
user, Pleroma.Repo.all(Pleroma.Activity.Queries.by_type("Create"))
)
generate_dms(user, users, opts)
{:ok, activity} = generate_long_thread(user, users, opts)

View File

@ -180,7 +180,8 @@ config :pleroma, Pleroma.Web.Endpoint,
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
level: :debug,
format: "\n$time $metadata[$level] $message\n",
metadata: [:request_id]
config :logger, :ex_syslogger,
@ -208,6 +209,7 @@ config :tesla, adapter: Tesla.Adapter.Hackney
config :pleroma, :http,
proxy_url: nil,
send_user_agent: true,
user_agent: :default,
adapter: [
ssl_options: [
# Workaround for remote server certificate chain issues

View File

@ -20,7 +20,8 @@ config :pleroma, Pleroma.Web.Endpoint,
config :phoenix, serve_endpoints: true
# Do not print debug messages in production
config :logger, level: :warn
config :logger, :console, level: :warn
config :logger, :ex_syslogger, level: :warn
# ## SSL Support
#

View File

@ -1,6 +1,6 @@
import Config
config :pleroma, :instance, static: "/var/lib/pleroma/static"
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"

View File

@ -15,7 +15,9 @@ config :pleroma, Pleroma.Captcha,
method: Pleroma.Captcha.Mock
# Print only warnings and errors during test
config :logger, level: :warn
config :logger, :console,
level: :warn,
format: "\n[$level] $message\n"
config :pleroma, :auth, oauth_consumer_strategies: []

View File

@ -870,3 +870,19 @@ Compile time settings (need instance reboot):
- Authentication: required
- Params: None
- Response: JSON, "ok" and 200 status
## `PATCH /api/pleroma/admin/users/confirm_email`
### Confirm users' emails
- Params:
- `nicknames`
- Response: Array of user nicknames
## `PATCH /api/pleroma/admin/users/resend_confirmation_email`
### Resend confirmation email
- Params:
- `nicknames`
- Response: Array of user nicknames

View File

@ -57,6 +57,7 @@ Has these additional fields under the `pleroma` object:
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
- `deactivated`: boolean, true when the user is deactivated
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
### Source
@ -91,6 +92,12 @@ Has these additional fields under the `pleroma` object:
- `is_seen`: true if the notification was read by the user
### Move Notification
The `type` value is `move`. Has an additional field:
- `target`: new account
## GET `/api/v1/notifications`
Accepts additional parameters:
@ -136,6 +143,7 @@ Additional parameters can be added to the JSON body/Form data:
- `default_scope` - the scope returned under `privacy` key in Source subentity
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
- `skip_thread_containment` - if true, skip filtering out broken threads
- `allow_following_move` - if true, allows automatically follow moved following accounts
- `pleroma_background_image` - sets the background image of the user.
### Pleroma Settings Store

View File

@ -15,6 +15,11 @@ $PREFIX new <nickname> <email> [<options>]
- `--admin`/`--no-admin` - whether the user should be an admin
- `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
## List local users
```sh
$PREFIX list
```
## Generate an invite link
```sh
$PREFIX invite [<options>]

View File

@ -348,7 +348,17 @@ Available caches:
* `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration).
* `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds).
## :hackney_pools
## HTTP client
### :http
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`)
* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`)
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
* `adapter`: array of hackney options
### :hackney_pools
Advanced. Tweaks Hackney (http client) connections pools.

View File

@ -6,6 +6,11 @@ defmodule Mix.Pleroma do
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do
Application.put_env(:logger, :console, level: :debug)
end
{:ok, _} = Application.ensure_all_started(:pleroma)
end

View File

@ -362,6 +362,24 @@ defmodule Mix.Tasks.Pleroma.User do
end
end
def run(["list"]) do
start_pleroma()
Pleroma.User.Query.build(%{local: true})
|> Pleroma.RepoStreamer.chunk_stream(500)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
shell_info(
"#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{
user.info.locked
}, deactivated: #{user.info.deactivated}"
)
end)
end)
|> Stream.run()
end
defp set_moderator(user, value) do
{:ok, user} =
user

View File

@ -28,7 +28,8 @@ defmodule Pleroma.Activity do
"Create" => "mention",
"Follow" => "follow",
"Announce" => "reblog",
"Like" => "favourite"
"Like" => "favourite",
"Move" => "move"
}
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
@ -303,4 +304,17 @@ defmodule Pleroma.Activity do
end
defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search
def direct_conversation_id(activity, for_user) do
alias Pleroma.Conversation.Participation
with %{data: %{"context" => context}} when is_binary(context) <- activity,
%Pleroma.Conversation{} = conversation <- Pleroma.Conversation.get_for_ap_id(context),
%Participation{id: participation_id} <-
Participation.for_user_and_conversation(for_user, conversation) do
participation_id
else
_ -> nil
end
end
end

View File

@ -17,8 +17,14 @@ defmodule Pleroma.Application do
def repository, do: @repository
def user_agent do
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
named_version() <> "; " <> info
case Pleroma.Config.get([:http, :user_agent], :default) do
:default ->
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
named_version() <> "; " <> info
custom ->
custom
end
end
# See http://elixir-lang.org/docs/stable/elixir/Application.html

View File

@ -101,10 +101,28 @@ defmodule Pleroma.FollowingRelationship do
|> select([r, u], u.follower_address)
|> Repo.all()
if not user.local or user.nickname in [nil, "internal.fetch"] do
if not user.local or user.invisible do
following
else
[user.follower_address | following]
end
end
def move_following(origin, target) do
__MODULE__
|> join(:inner, [r], f in assoc(r, :follower))
|> where(following_id: ^origin.id)
|> where([r, f], f.allow_following_move == true)
|> limit(50)
|> preload([:follower])
|> Repo.all()
|> Enum.map(fn following_relationship ->
Repo.delete(following_relationship)
Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
end)
|> case do
[] -> :ok
_ -> move_following(origin, target)
end
end
end

View File

@ -624,7 +624,31 @@ defmodule Pleroma.ModerationLog do
"subject" => subjects
}
}) do
"@#{actor_nickname} force password reset for users: #{users_to_nicknames_string(subjects)}"
"@#{actor_nickname} forced password reset for users: #{users_to_nicknames_string(subjects)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "confirm_email",
"subject" => subjects
}
}) do
"@#{actor_nickname} confirmed email for users: #{users_to_nicknames_string(subjects)}"
end
@spec get_log_entry_message(ModerationLog) :: String.t()
def get_log_entry_message(%ModerationLog{
data: %{
"actor" => %{"nickname" => actor_nickname},
"action" => "resend_confirmation_email",
"subject" => subjects
}
}) do
"@#{actor_nickname} re-sent confirmation email for users: #{
users_to_nicknames_string(subjects)
}"
end
defp nicknames_to_string(nicknames) do

View File

@ -251,10 +251,13 @@ defmodule Pleroma.Notification do
end
end
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
when type in ["Like", "Announce", "Follow"] do
users = get_notified_from_activity(activity)
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
def create_notifications(%Activity{data: %{"type" => type}} = activity)
when type in ["Like", "Announce", "Follow", "Move"] do
notifications =
activity
|> get_notified_from_activity()
|> Enum.map(&create_notification(activity, &1))
{:ok, notifications}
end
@ -276,19 +279,15 @@ defmodule Pleroma.Notification do
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(
%Activity{data: %{"to" => _, "type" => type} = _data} = activity,
local_only
)
when type in ["Create", "Like", "Announce", "Follow"] do
recipients =
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
|> Utils.maybe_notify_subscribers(activity)
|> Enum.uniq()
User.get_users_from_set(recipients, local_only)
def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
when type in ["Create", "Like", "Announce", "Follow", "Move"] do
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
|> Utils.maybe_notify_subscribers(activity)
|> Utils.maybe_notify_followers(activity)
|> Enum.uniq()
|> User.get_users_from_set(local_only)
end
def get_notified_from_activity(_, _local_only), do: []

View File

@ -63,7 +63,7 @@ defmodule Pleroma.Object do
end
defp warn_on_no_object_preloaded(ap_id) do
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object"
"Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object"
|> Logger.debug()
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
@ -255,4 +255,8 @@ defmodule Pleroma.Object do
|> Object.change(%{data: Map.merge(data || %{}, attrs)})
|> Repo.update()
end
def local?(%Object{data: %{"id" => id}}) do
String.starts_with?(id, Pleroma.Web.base_url() <> "/")
end
end

View File

@ -49,7 +49,7 @@ defmodule Pleroma.Object.Fetcher do
end
def refetch_object(%Object{data: %{"id" => id}} = object) do
with {:local, false} <- {:local, String.starts_with?(id, Pleroma.Web.base_url() <> "/")},
with {:local, false} <- {:local, Object.local?(object)},
{:ok, data} <- fetch_and_contain_remote_object_from_id(id),
{:ok, object} <- reinject_object(object, data) do
{:ok, object}

View File

@ -67,8 +67,7 @@ defmodule Pleroma.User do
field(:source_data, :map, default: %{})
field(:note_count, :integer, default: 0)
field(:follower_count, :integer, default: 0)
# Should be filled in only for remote users
field(:following_count, :integer, default: nil)
field(:following_count, :integer, default: 0)
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false)
@ -104,7 +103,9 @@ defmodule Pleroma.User do
field(:raw_fields, {:array, :map}, default: [])
field(:discoverable, :boolean, default: false)
field(:invisible, :boolean, default: false)
field(:allow_following_move, :boolean, default: true)
field(:skip_thread_containment, :boolean, default: false)
field(:also_known_as, {:array, :string}, default: [])
field(:notification_settings, :map,
default: %{
@ -119,8 +120,6 @@ defmodule Pleroma.User do
has_many(:registrations, Registration)
has_many(:deliveries, Delivery)
field(:info, :map, default: %{})
timestamps()
end
@ -134,6 +133,8 @@ defmodule Pleroma.User do
def visible_for?(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false
def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true
def visible_for?(%User{} = user, for_user) do
@ -176,22 +177,6 @@ defmodule Pleroma.User do
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
def user_info(%User{} = user, args \\ %{}) do
following_count =
Map.get(args, :following_count, user.following_count || following_count(user))
follower_count = Map.get(args, :follower_count, user.follower_count)
%{
note_count: user.note_count,
locked: user.locked,
confirmation_pending: user.confirmation_pending,
default_scope: user.default_scope
}
|> Map.put(:following_count, following_count)
|> Map.put(:follower_count, follower_count)
end
def follow_state(%User{} = user, %User{} = target) do
case Utils.fetch_latest_follow(user, target) do
%{data: %{"state" => state}} -> state
@ -210,10 +195,6 @@ defmodule Pleroma.User do
Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
end
def set_info_cache(user, args) do
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
end
@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
def restrict_deactivated(query) do
from(u in query, where: u.deactivated != ^true)
@ -244,7 +225,6 @@ defmodule Pleroma.User do
params =
params
|> Map.put(:info, params[:info] || %{})
|> truncate_if_exists(:name, name_limit)
|> truncate_if_exists(:bio, bio_limit)
|> truncate_fields_param()
@ -273,7 +253,8 @@ defmodule Pleroma.User do
:fields,
:following_count,
:discoverable,
:invisible
:invisible,
:also_known_as
]
)
|> validate_required([:name, :ap_id])
@ -315,13 +296,15 @@ defmodule Pleroma.User do
:hide_followers_count,
:hide_follows_count,
:hide_favorites,
:allow_following_move,
:background,
:show_role,
:skip_thread_containment,
:fields,
:raw_fields,
:pleroma_settings_store,
:discoverable
:discoverable,
:also_known_as
]
)
|> unique_constraint(:nickname)
@ -359,9 +342,11 @@ defmodule Pleroma.User do
:hide_follows,
:fields,
:hide_followers,
:allow_following_move,
:discoverable,
:hide_followers_count,
:hide_follows_count
:hide_follows_count,
:also_known_as
]
)
|> unique_constraint(:nickname)
@ -492,6 +477,10 @@ defmodule Pleroma.User do
end
end
def try_send_confirmation_email(users) do
Enum.each(users, &try_send_confirmation_email/1)
end
def needs_update?(%User{local: true}), do: false
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
@ -522,14 +511,9 @@ defmodule Pleroma.User do
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
def follow_all(follower, followeds) do
followeds =
Enum.reject(followeds, fn followed ->
blocks?(follower, followed) || blocks?(followed, follower)
end)
Enum.each(followeds, &follow(follower, &1, "accept"))
Enum.each(followeds, &update_follower_count/1)
followeds
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|> Enum.each(&follow(follower, &1, "accept"))
set_cache(follower)
end
@ -549,11 +533,11 @@ defmodule Pleroma.User do
true ->
FollowingRelationship.follow(follower, followed, state)
follower = maybe_update_following_count(follower)
{:ok, _} = update_follower_count(followed)
set_cache(follower)
follower
|> update_following_count()
|> set_cache()
end
end
@ -561,11 +545,12 @@ defmodule Pleroma.User do
if following?(follower, followed) and follower.ap_id != followed.ap_id do
FollowingRelationship.unfollow(follower, followed)
follower = maybe_update_following_count(follower)
{:ok, followed} = update_follower_count(followed)
set_cache(follower)
{:ok, follower} =
follower
|> update_following_count()
|> set_cache()
{:ok, follower, Utils.fetch_latest_follow(follower, followed)}
else
@ -615,7 +600,6 @@ defmodule Pleroma.User do
def set_cache(%User{} = user) do
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
{:ok, user}
end
@ -634,7 +618,6 @@ defmodule Pleroma.User do
def invalidate_cache(user) do
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
Cachex.del(:user_cache, "nickname:#{user.nickname}")
Cachex.del(:user_cache, "user_info:#{user.id}")
end
def get_cached_by_ap_id(ap_id) do
@ -702,11 +685,6 @@ defmodule Pleroma.User do
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
end
def get_cached_user_info(user) do
key = "user_info:#{user.id}"
Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
end
def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)
def get_or_fetch_by_nickname(nickname) do
@ -895,8 +873,8 @@ defmodule Pleroma.User do
end
end
@spec maybe_update_following_count(User.t()) :: User.t()
def maybe_update_following_count(%User{local: false} = user) do
@spec update_following_count(User.t()) :: User.t()
def update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
maybe_fetch_follow_information(user)
else
@ -904,7 +882,13 @@ defmodule Pleroma.User do
end
end
def maybe_update_following_count(user), do: user
def update_following_count(%User{local: true} = user) do
following_count = FollowingRelationship.following_count(user)
user
|> follow_information_changeset(%{following_count: following_count})
|> Repo.update!()
end
def set_unread_conversation_count(%User{local: true} = user) do
unread_query = Participation.unread_conversation_count_for_user(user)
@ -1097,7 +1081,12 @@ defmodule Pleroma.User do
def deactivate(%User{} = user, status) do
with {:ok, user} <- set_activation_status(user, status) do
Enum.each(get_followers(user), &invalidate_cache/1)
user
|> get_followers()
|> Enum.filter(& &1.local)
|> Enum.each(fn follower ->
follower |> update_following_count() |> set_cache()
end)
# Only update local user counts, remote will be update during the next pull.
user
@ -1226,7 +1215,7 @@ defmodule Pleroma.User do
def external_users(opts \\ []) do
query =
external_users_query()
|> select([u], struct(u, [:id, :ap_id, :info]))
|> select([u], struct(u, [:id, :ap_id]))
query =
if opts[:max_id],
@ -1317,22 +1306,23 @@ defmodule Pleroma.User do
end
end
@doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
@doc """
Creates an internal service actor by URI if missing.
Optionally takes nickname for addressing.
"""
def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
with %User{} = user <- get_cached_by_ap_id(uri) do
user
else
_ ->
{:ok, user} =
%User{}
|> cast(%{}, [:ap_id, :nickname, :local])
|> put_change(:ap_id, uri)
|> put_change(:nickname, nickname)
|> put_change(:local, true)
|> put_change(:follower_address, uri <> "/followers")
|> Repo.insert()
with user when is_nil(user) <- get_cached_by_ap_id(uri) do
{:ok, user} =
%User{
invisible: true,
local: true,
ap_id: uri,
nickname: nickname,
follower_address: uri <> "/followers"
}
|> Repo.insert()
user
user
end
end
@ -1575,6 +1565,11 @@ defmodule Pleroma.User do
|> update_and_set_cache()
end
@spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}]
def toggle_confirmation(users) do
Enum.map(users, &toggle_confirmation/1)
end
def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
mascot
end

View File

@ -45,6 +45,7 @@ defmodule Pleroma.User.Search do
for_user
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_invisible_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
|> trigram_rank(query_string)
@ -98,6 +99,10 @@ defmodule Pleroma.User.Search do
defp base_query(_user, false), do: User
defp base_query(user, true), do: User.get_followers_query(user)
defp filter_invisible_users(query) do
from(q in query, where: q.invisible == false)
end
defp filter_blocked_user(query, %User{blocks: blocks})
when length(blocks) > 0 do
from(q in query, where: not (q.ap_id in ^blocks))

View File

@ -541,6 +541,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
def move(%User{} = origin, %User{} = target, local \\ true) do
params = %{
"type" => "Move",
"actor" => origin.ap_id,
"object" => origin.ap_id,
"target" => target.ap_id
}
with true <- origin.ap_id in target.also_known_as,
{:ok, activity} <- insert(params, local) do
maybe_federate(activity)
BackgroundWorker.enqueue("move_following", %{
"origin_id" => origin.id,
"target_id" => target.id
})
{:ok, activity}
else
false -> {:error, "Target account must have the origin in `alsoKnownAs`"}
err -> err
end
end
defp fetch_activities_for_context_query(context, opts) do
public = [Pleroma.Constants.as_public()]
@ -734,6 +758,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Enum.reverse()
end
def fetch_instance_activities(params) do
params =
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("instance", params["instance"])
|> Map.put("whole_db", true)
fetch_activities([Pleroma.Constants.as_public()], params, :offset)
|> Enum.reverse()
end
defp user_activities_recipients(%{"godmode" => true}) do
[]
end
@ -961,6 +996,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_muted_reblogs(query, _), do: query
defp restrict_instance(query, %{"instance" => instance}) do
users =
from(
u in User,
select: u.ap_id,
where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
)
|> Repo.all()
from(activity in query, where: activity.actor in ^users)
end
defp restrict_instance(query, _), do: query
defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query
defp exclude_poll_votes(query, _) do
@ -1041,6 +1090,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_reblogs(opts)
|> restrict_pinned(opts)
|> restrict_muted_reblogs(opts)
|> restrict_instance(opts)
|> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts)
|> exclude_visibility(opts)
@ -1145,7 +1195,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
name: data["name"],
follower_address: data["followers"],
following_address: data["following"],
bio: data["summary"]
bio: data["summary"],
also_known_as: Map.get(data, "alsoKnownAs", [])
}
# nickname can be nil because of virtual actors
@ -1207,13 +1258,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp collection_private(data) do
if is_map(data["first"]) and
data["first"]["type"] in ["CollectionPage", "OrderedCollectionPage"] do
defp collection_private(%{"first" => first}) do
if is_map(first) and
first["type"] in ["CollectionPage", "OrderedCollectionPage"] do
{:ok, false}
else
with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <-
Fetcher.fetch_and_contain_remote_object_from_id(data["first"]) do
Fetcher.fetch_and_contain_remote_object_from_id(first) do
{:ok, false}
else
{:error, {:ok, %{status: code}}} when code in [401, 403] ->
@ -1228,6 +1279,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp collection_private(_data), do: {:ok, true}
def user_data_from_user_object(data) do
with {:ok, data} <- MRF.filter(data),
{:ok, data} <- object_to_user_data(data) do

View File

@ -45,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
end
def user(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- User.ensure_keys_present(user) do
conn
|> put_resp_content_type("application/activity+json")
@ -53,6 +53,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> render("user.json", %{user: user})
else
nil -> {:error, :not_found}
%{local: false} -> {:error, :not_found}
end
end

View File

@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.Relay do
relay_ap_id()
|> User.get_or_create_service_actor_by_ap_id()
{:ok, actor} = User.set_invisible(actor, true)
actor
end

View File

@ -669,7 +669,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
update_data =
new_user_data
|> Map.take([:avatar, :banner, :bio, :name])
|> Map.take([:avatar, :banner, :bio, :name, :also_known_as])
|> Map.put(:fields, fields)
|> Map.put(:locked, locked)
|> Map.put(:invisible, invisible)
@ -857,6 +857,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
def handle_incoming(
%{
"type" => "Move",
"actor" => origin_actor,
"object" => origin_actor,
"target" => target_actor
},
_options
) do
with %User{} = origin_user <- User.get_cached_by_ap_id(origin_actor),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_actor),
true <- origin_actor in target_user.also_known_as do
ActivityPub.move(origin_user, target_user, false)
else
_e -> :error
end
end
def handle_incoming(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil

View File

@ -903,7 +903,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def strip_report_status_data(activity) do
[actor | reported_activities] = activity.data["object"]
stripped_activities = Enum.map(reported_activities, & &1["id"])
stripped_activities =
Enum.map(reported_activities, fn
act when is_map(act) -> act["id"]
act when is_binary(act) -> act
end)
new_data = put_in(activity.data, ["object"], [actor | stripped_activities])
{:ok, %{activity | data: new_data}}

View File

@ -14,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
@spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: %{"type" => "Move"}}), do: true
def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false
def is_public?(data), do: Utils.label_in_message?(Pleroma.Constants.as_public(), data)

View File

@ -229,6 +229,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
def list_instance_statuses(conn, %{"instance" => instance} = params) do
{page, page_size} = page_params(params)
activities =
ActivityPub.fetch_instance_activities(%{
"instance" => instance,
"limit" => page_size,
"offset" => (page - 1) * page_size
})
conn
|> put_view(StatusView)
|> render("index.json", %{activities: activities, as: :activity})
end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
godmode = params["godmode"] == "true" || params["godmode"] == true
@ -337,7 +352,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
}
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
{:ok, users, count} <- filter_relay_user(users, count),
{:ok, users, count} <- filter_service_users(users, count),
do:
conn
|> json(
@ -349,15 +364,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
)
end
defp filter_relay_user(users, count) do
filtered_users = Enum.reject(users, &relay_user?/1)
count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
defp filter_service_users(users, count) do
filtered_users = Enum.reject(users, &service_user?/1)
count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
{:ok, filtered_users, count}
end
defp relay_user?(user) do
user.ap_id == Relay.relay_ap_id()
defp service_user?(user) do
String.match?(user.ap_id, ~r/.*\/relay$/) or
String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
end
@filters ~w(local external active deactivated is_admin is_moderator)
@ -801,6 +817,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
conn |> json("ok")
end
def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
User.toggle_confirmation(users)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "confirm_email"
})
conn |> json("")
end
def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
User.try_send_confirmation_email(users)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "resend_confirmation_email"
})
conn |> json("")
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)

View File

@ -36,7 +36,8 @@ defmodule Pleroma.Web.AdminAPI.AccountView do
"deactivated" => user.deactivated,
"local" => user.local,
"roles" => User.roles(user),
"tags" => user.tags || []
"tags" => user.tags || [],
"confirmation_pending" => user.confirmation_pending
}
end

View File

@ -451,6 +451,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
recipients ++ to
end
def maybe_notify_to_recipients(recipients, _), do: recipients
def maybe_notify_mentioned_recipients(
recipients,
%Activity{data: %{"to" => _to, "type" => type} = data} = activity
@ -502,6 +504,17 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def maybe_notify_subscribers(recipients, _), do: recipients
def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do
with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do
user
|> User.get_followers()
|> Enum.map(& &1.ap_id)
|> Enum.concat(recipients)
end
end
def maybe_notify_followers(recipients, _), do: recipients
def maybe_extract_mentions(%{"tag" => tag}) do
tag
|> Enum.filter(fn x -> is_map(x) && x["type"] == "Mention" end)

View File

@ -152,6 +152,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
:hide_favorites,
:show_role,
:skip_thread_containment,
:allow_following_move,
:discoverable
]
|> Enum.reduce(%{}, fn key, acc ->
@ -238,7 +239,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
@doc "GET /api/v1/accounts/:id"
def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
true <- User.visible_for?(user, for_user) do
render(conn, "show.json", user: user, for: for_user)
else
_e -> render_error(conn, :not_found, "Can't find user")

View File

@ -71,18 +71,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user)
following_count =
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
user_info.following_count
user.following_count || 0
else
0
end
followers_count =
if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do
user_info.follower_count
user.follower_count || 0
else
0
end
@ -144,7 +143,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
# Pleroma extension
pleroma: %{
confirmation_pending: user_info.confirmation_pending,
confirmation_pending: user.confirmation_pending,
tags: user.tags,
hide_followers_count: user.hide_followers_count,
hide_follows_count: user.hide_follows_count,
@ -157,12 +156,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
}
}
|> maybe_put_role(user, opts[:for])
|> maybe_put_settings(user, opts[:for], user_info)
|> maybe_put_settings(user, opts[:for], opts)
|> maybe_put_notification_settings(user, opts[:for])
|> maybe_put_settings_store(user, opts[:for], opts)
|> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
|> maybe_put_follow_requests_count(user, opts[:for])
|> maybe_put_allow_following_move(user, opts[:for])
|> maybe_put_unread_conversation_count(user, opts[:for])
end
@ -191,7 +191,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
data,
%User{id: user_id} = user,
%User{id: user_id},
_user_info
_opts
) do
data
|> Kernel.put_in([:source, :privacy], user.default_scope)
@ -239,6 +239,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_notification_settings(data, _, _), do: data
defp maybe_put_allow_following_move(data, %User{id: user_id} = user, %User{id: user_id}) do
Kernel.put_in(data, [:pleroma, :allow_following_move], user.allow_following_move)
end
defp maybe_put_allow_following_move(data, _, _), do: data
defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
Kernel.put_in(data, [:pleroma, :deactivated], user.deactivated)
end

View File

@ -37,32 +37,24 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
}
case mastodon_type do
"mention" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: activity, for: user})
})
"favourite" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
})
"reblog" ->
response
|> Map.merge(%{
status: StatusView.render("show.json", %{activity: parent_activity, for: user})
})
"follow" ->
response
_ ->
nil
"mention" -> put_status(response, activity, user)
"favourite" -> put_status(response, parent_activity, user)
"reblog" -> put_status(response, parent_activity, user)
"move" -> put_target(response, activity, user)
"follow" -> response
_ -> nil
end
else
_ -> nil
end
end
defp put_status(response, activity, user) do
Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user}))
end
defp put_target(response, activity, user) do
target = User.get_cached_by_ap_id(activity.data["target"])
Map.put(response, :target, AccountView.render("show.json", %{user: target, for: user}))
end
end

View File

@ -9,8 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
@ -245,12 +243,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
direct_conversation_id =
with {_, nil} <- {:direct_conversation_id, opts[:direct_conversation_id]},
{_, true} <- {:include_id, opts[:with_direct_conversation_id]},
{_, %User{} = for_user} <- {:for_user, opts[:for]},
%{data: %{"context" => context}} when is_binary(context) <- activity,
%Conversation{} = conversation <- Conversation.get_for_ap_id(context),
%Participation{id: participation_id} <-
Participation.for_user_and_conversation(for_user, conversation) do
participation_id
{_, %User{} = for_user} <- {:for_user, opts[:for]} do
Activity.direct_conversation_id(activity, for_user)
else
{:direct_conversation_id, participation_id} when is_integer(participation_id) ->
participation_id

View File

@ -11,7 +11,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Metadata.PlayerView
@ -38,11 +37,9 @@ defmodule Pleroma.Web.OStatus.OStatusController do
with id <- o_status_url(conn, :object, uuid),
{_, %Activity{} = activity} <-
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{_, true} <- {:public?, Visibility.is_public?(activity)} do
case format do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, nil, activity, user)
_ -> redirect(conn, to: "/notice/#{activity.id}")
end
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
@ -61,11 +58,9 @@ defmodule Pleroma.Web.OStatus.OStatusController do
def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
with id <- o_status_url(conn, :activity, uuid),
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
{_, true} <- {:public?, Visibility.is_public?(activity)} do
case format do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
_ -> represent_activity(conn, format, activity, user)
_ -> redirect(conn, to: "/notice/#{activity.id}")
end
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
@ -81,7 +76,15 @@ defmodule Pleroma.Web.OStatus.OStatusController do
{_, true} <- {:public?, Visibility.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
cond do
format == "html" && activity.data["type"] == "Create" ->
format in ["json", "activity+json"] ->
if activity.local do
%{data: %{"id" => redirect_url}} = Object.normalize(activity)
redirect(conn, external: redirect_url)
else
{:error, :not_found}
end
activity.data["type"] == "Create" ->
%Object{} = object = Object.normalize(activity)
RedirectController.redirector_with_meta(
@ -94,11 +97,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do
}
)
format == "html" ->
RedirectController.redirector(conn, nil)
true ->
represent_activity(conn, format, activity, user)
RedirectController.redirector(conn, nil)
end
else
reason when reason in [{:public?, false}, {:activity, nil}] ->
@ -135,24 +135,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end
end
defp represent_activity(
conn,
"activity+json",
%Activity{data: %{"type" => "Create"}} = activity,
_user
) do
object = Object.normalize(activity)
conn
|> put_resp_header("content-type", "application/activity+json")
|> put_view(ObjectView)
|> render("object.json", %{object: object})
end
defp represent_activity(_conn, _, _, _) do
{:error, :not_found}
end
def errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found")
end

View File

@ -16,7 +16,7 @@ defmodule Pleroma.Web.Push.Impl do
require Logger
import Ecto.Query
@types ["Create", "Follow", "Announce", "Like"]
@types ["Create", "Follow", "Announce", "Like", "Move"]
@doc "Performs sending notifications for user subscriptions"
@spec perform(Notification.t()) :: list(any) | :error
@ -33,6 +33,8 @@ defmodule Pleroma.Web.Push.Impl do
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor)
object = Object.normalize(activity)
user = User.get_cached_by_id(user_id)
direct_conversation_id = Activity.direct_conversation_id(activity, user)
for subscription <- fetch_subsriptions(user_id),
get_in(subscription.data, ["alerts", type]) do
@ -45,7 +47,8 @@ defmodule Pleroma.Web.Push.Impl do
icon: avatar_url,
preferred_locale: "en",
pleroma: %{
activity_id: activity_id
activity_id: activity_id,
direct_conversation_id: direct_conversation_id
}
}
|> Jason.encode!()

View File

@ -178,6 +178,11 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname", AdminAPIController, :user_show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/instances/:instance/statuses", AdminAPIController, :list_instance_statuses)
patch("/users/confirm_email", AdminAPIController, :confirm_email)
patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
get("/reports", AdminAPIController, :list_reports)
get("/grouped_reports", AdminAPIController, :list_grouped_reports)
get("/reports/:id", AdminAPIController, :report_show)

View File

@ -71,4 +71,11 @@ defmodule Pleroma.Workers.BackgroundWorker do
activity = Activity.get_by_id(activity_id)
Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
end
def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do
origin = User.get_cached_by_id(origin_id)
target = User.get_cached_by_id(target_id)
Pleroma.FollowingRelationship.move_following(origin, target)
end
end

24
mix.exs
View File

@ -102,7 +102,7 @@ defmodule Pleroma.Mixfile do
{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~> 3.2"},
{:postgrex, ">= 0.13.5"},
{:oban, "~> 0.8.1"},
{:oban, "~> 0.12.0"},
{:quantum, "~> 2.3"},
{:gettext, "~> 0.15"},
{:comeonin, "~> 4.1.1"},
@ -194,27 +194,21 @@ defmodule Pleroma.Mixfile do
identifier_filter = ~r/[^0-9a-z\-]+/i
# Pre-release version, denoted from patch version with a hyphen
{git_tag, git_pre_release} =
git_pre_release =
with {tag, 0} <-
System.cmd("git", ["describe", "--tags", "--abbrev=0"], stderr_to_stdout: true),
tag = String.trim(tag),
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]),
describe = String.trim(describe),
ahead <- String.replace(describe, tag, ""),
ahead <- String.trim_leading(ahead, "-") do
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
{describe, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=8"]) do
describe
|> String.trim()
|> String.replace(String.trim(tag), "")
|> String.trim_leading("-")
|> String.trim()
else
_ ->
{commit_hash, 0} = System.cmd("git", ["rev-parse", "--short", "HEAD"])
{nil, "0-g" <> String.trim(commit_hash)}
"0-g" <> String.trim(commit_hash)
end
if git_tag && version != git_tag do
Mix.shell().error(
"Application version #{inspect(version)} does not match git tag #{inspect(git_tag)}"
)
end
# Branch name as pre-release version component, denoted with a dot
branch_name =
with {branch_name, 0} <- System.cmd("git", ["rev-parse", "--abbrev-ref", "HEAD"]),

View File

@ -23,8 +23,8 @@
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.2.3", "51274df79862845b388733fddcf6f107d0c8c86e27abe7131fa98f8d30761bda", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@ -38,7 +38,7 @@
"fast_html": {:hex, :fast_html, "0.99.4", "d80812664f0429607e1d880fba0ef04da87a2e4fa596701bcaae17953535695c", [:make, :mix], [], "hexpm"},
"fast_sanitize": {:hex, :fast_sanitize, "0.1.4", "6c2e7203ca2f8275527a3021ba6e9d5d4ee213a47dc214a97c128737c9e56df1", [:mix], [{:fast_html, "~> 0.99", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"},
"floki": {:hex, :floki, "0.23.0", "956ab6dba828c96e732454809fb0bd8d43ce0979b75f34de6322e73d4c917829", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"},
"floki": {:hex, :floki, "0.23.1", "e100306ce7d8841d70a559748e5091542e2cfc67ffb3ade92b89a8435034dab1", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm"},
"gen_smtp": {:hex, :gen_smtp, "0.15.0", "9f51960c17769b26833b50df0b96123605a8024738b62db747fece14eb2fbfcc", [:rebar3], [], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.14.3", "d0c66f1c87faa301c1a85a809a3ee9097a4264b2edf7644bf5c123237ef732bf", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
@ -67,7 +67,7 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "0.8.1", "4bbf62eb1829f856d69aeb5069ac7036afe07db8221a17de2a9169cc7a58a318", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"oban": {:hex, :oban, "0.12.0", "5477d5ab4a5a201c0b6c89764040ebfc5d2c71c488a36f378016ce5990838f0f", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
@ -97,7 +97,7 @@
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -0,0 +1,10 @@
defmodule Pleroma.Repo.Migrations.AddMoveSupportToUsers do
use Ecto.Migration
def change do
alter table(:users) do
add(:also_known_as, {:array, :string}, default: [], null: false)
add(:allow_following_move, :boolean, default: true, null: false)
end
end
end

View File

@ -0,0 +1,22 @@
defmodule Pleroma.Repo.Migrations.SetVisibleServiceActors do
use Ecto.Migration
import Ecto.Query
alias Pleroma.Repo
def up do
user_nicknames = ["relay", "internal.fetch"]
from(
u in "users",
where: u.nickname in ^user_nicknames,
update: [
set: [invisible: true]
]
)
|> Repo.update_all([])
end
def down do
:ok
end
end

View File

@ -0,0 +1,9 @@
defmodule Pleroma.Repo.Migrations.RemoveInfoFromUsers do
use Ecto.Migration
def change do
alter table(:users) do
remove(:info, :map, default: %{})
end
end
end

View File

@ -0,0 +1,53 @@
defmodule Pleroma.Repo.Migrations.FixMissingFollowingCount do
use Ecto.Migration
def up do
"""
UPDATE
users
SET
following_count = sub.count
FROM
(
SELECT
users.id AS sub_id
,COUNT (following_relationships.id)
FROM
following_relationships
,users
WHERE
users.id = following_relationships.follower_id
AND following_relationships.state = 'accept'
GROUP BY
users.id
) AS sub
WHERE
users.id = sub.sub_id
AND users.local = TRUE
;
"""
|> execute()
"""
UPDATE
users
SET
following_count = 0
WHERE
following_count IS NULL
"""
|> execute()
execute("ALTER TABLE users
ALTER COLUMN following_count SET DEFAULT 0,
ALTER COLUMN following_count SET NOT NULL
")
end
def down do
execute("ALTER TABLE users
ALTER COLUMN following_count DROP DEFAULT,
ALTER COLUMN following_count DROP NOT NULL
")
end
end

View File

@ -29,7 +29,11 @@
"@id": "litepub:oauthRegistrationEndpoint",
"@type": "@id"
},
"EmojiReaction": "litepub:EmojiReaction"
"EmojiReaction": "litepub:EmojiReaction",
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
}
}
]
}

View File

@ -8,5 +8,5 @@
# fi
# Set the release to work across nodes
export RELEASE_DISTRIBUTION=name
export RELEASE_NODE=<%= @release.name %>@127.0.0.1
export RELEASE_DISTRIBUTION="${RELEASE_DISTRIBUTION:-name}"
export RELEASE_NODE="${RELEASE_NODE:-<%= @release.name %>@127.0.0.1}"

View File

@ -0,0 +1,47 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Integration.FederationTest do
use Pleroma.DataCase
@moduletag :federated
import Pleroma.Cluster
setup_all do
Pleroma.Cluster.spawn_default_cluster()
:ok
end
@federated1 :"federated1@127.0.0.1"
describe "federated cluster primitives" do
test "within/2 captures local bindings and executes block on remote node" do
captured_binding = :captured
result =
within @federated1 do
user = Pleroma.Factory.insert(:user)
{captured_binding, node(), user}
end
assert {:captured, @federated1, user} = result
refute Pleroma.User.get_by_id(user.id)
assert user.id == within(@federated1, do: Pleroma.User.get_by_id(user.id)).id
end
test "runs webserver on customized port" do
{nickname, url, url_404} =
within @federated1 do
import Pleroma.Web.Router.Helpers
user = Pleroma.Factory.insert(:user)
user_url = account_url(Pleroma.Web.Endpoint, :show, user)
url_404 = account_url(Pleroma.Web.Endpoint, :show, "not-exists")
{user.nickname, user_url, url_404}
end
assert {:ok, {{_, 200, _}, _headers, body}} = :httpc.request(~c"#{url}")
assert %{"acct" => ^nickname} = Jason.decode!(body)
assert {:ok, {{_, 404, _}, _headers, _body}} = :httpc.request(~c"#{url_404}")
end
end
end

View File

@ -9,7 +9,11 @@
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
"conversation": "ostatus:conversation",
"toot": "http://joinmastodon.org/ns#",
"Emoji": "toot:Emoji"
"Emoji": "toot:Emoji",
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
}
}],
"id": "http://mastodon.example.org/users/admin",
"type": "Person",
@ -50,5 +54,6 @@
"type": "Image",
"mediaType": "image/png",
"url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
}
},
"alsoKnownAs": ["http://example.org/users/foo"]
}

View File

@ -0,0 +1,19 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"vcard": "http://www.w3.org/2006/vcard/ns#",
"dfrn": "http://purl.org/macgirvin/dfrn/1.0/",
"diaspora": "https://diasporafoundation.org/ns/",
"litepub": "http://litepub.social/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"Hashtag": "as:Hashtag",
"directMessage": "litepub:directMessage"
}
],
"id": "http://localhost:8080/followers/fuser3",
"type": "OrderedCollection",
"totalItems": 296
}

View File

@ -0,0 +1,19 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"vcard": "http://www.w3.org/2006/vcard/ns#",
"dfrn": "http://purl.org/macgirvin/dfrn/1.0/",
"diaspora": "https://diasporafoundation.org/ns/",
"litepub": "http://litepub.social/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"sensitive": "as:sensitive",
"Hashtag": "as:Hashtag",
"directMessage": "litepub:directMessage"
}
],
"id": "http://localhost:8080/following/fuser3",
"type": "OrderedCollection",
"totalItems": 32
}

View File

@ -0,0 +1,47 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FollowingRelationshipTest do
use Pleroma.DataCase
alias Pleroma.FollowingRelationship
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.Relay
import Pleroma.Factory
describe "following/1" do
test "returns following addresses without internal.fetch" do
user = insert(:user)
fetch_actor = InternalFetchActor.get_actor()
FollowingRelationship.follow(fetch_actor, user, "accept")
assert FollowingRelationship.following(fetch_actor) == [user.follower_address]
end
test "returns following addresses without relay" do
user = insert(:user)
relay_actor = Relay.get_actor()
FollowingRelationship.follow(relay_actor, user, "accept")
assert FollowingRelationship.following(relay_actor) == [user.follower_address]
end
test "returns following addresses without remote user" do
user = insert(:user)
actor = insert(:user, local: false)
FollowingRelationship.follow(actor, user, "accept")
assert FollowingRelationship.following(actor) == [user.follower_address]
end
test "returns following addresses with local user" do
user = insert(:user)
actor = insert(:user, local: true)
FollowingRelationship.follow(actor, user, "accept")
assert FollowingRelationship.following(actor) == [
actor.follower_address,
user.follower_address
]
end
end
end

View File

@ -228,5 +228,16 @@ defmodule Pleroma.HTMLTest do
assert url == "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=72255140"
end
test "does not crash when there is an HTML entity in a link" do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{"status" => "\"http://cofe.com/?boomer=ok&foo=bar\""})
object = Object.normalize(activity)
assert {:ok, nil} = HTML.extract_first_external_url(object, object.data["content"])
end
end
end

View File

@ -16,11 +16,21 @@ defmodule Pleroma.HTTP.RequestBuilderTest do
test "send pleroma user agent" do
Pleroma.Config.put([:http, :send_user_agent], true)
Pleroma.Config.put([:http, :user_agent], :default)
assert RequestBuilder.headers(%{}, []) == %{
headers: [{"User-Agent", Pleroma.Application.user_agent()}]
}
end
test "send custom user agent" do
Pleroma.Config.put([:http, :send_user_agent], true)
Pleroma.Config.put([:http, :user_agent], "totally-not-pleroma")
assert RequestBuilder.headers(%{}, []) == %{
headers: [{"User-Agent", "totally-not-pleroma"}]
}
end
end
describe "add_optional_params/3" do

View File

@ -630,6 +630,35 @@ defmodule Pleroma.NotificationTest do
assert Enum.empty?(Notification.for_user(local_user))
end
test "move activity generates a notification" do
%{ap_id: old_ap_id} = old_user = insert(:user)
%{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
follower = insert(:user)
other_follower = insert(:user, %{allow_following_move: false})
User.follow(follower, old_user)
User.follow(other_follower, old_user)
Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
ObanHelpers.perform_all()
assert [
%{
activity: %{
data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
}
}
] = Notification.for_user(follower)
assert [
%{
activity: %{
data: %{"type" => "Move", "actor" => ^old_ap_id, "target" => ^new_ap_id}
}
}
] = Notification.for_user(other_follower)
end
end
describe "for_user" do

218
test/support/cluster.ex Normal file
View File

@ -0,0 +1,218 @@
defmodule Pleroma.Cluster do
@moduledoc """
Facilities for managing a cluster of slave VM's for federated testing.
## Spawning the federated cluster
`spawn_cluster/1` spawns a map of slave nodes that are started
within the running VM. During startup, the slave node is sent all configuration
from the parent node, as well as all code. After receiving configuration and
code, the slave then starts all applications currently running on the parent.
The configuration passed to `spawn_cluster/1` overrides any parent application
configuration for the provided OTP app and key. This is useful for customizing
the Ecto database, Phoenix webserver ports, etc.
For example, to start a single federated VM named ":federated1", with the
Pleroma Endpoint running on port 4123, and with a database named
"pleroma_test1", you would run:
endpoint_conf = Application.fetch_env!(:pleroma, Pleroma.Web.Endpoint)
repo_conf = Application.fetch_env!(:pleroma, Pleroma.Repo)
Pleroma.Cluster.spawn_cluster(%{
:"federated1@127.0.0.1" => [
{:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test1")},
{:pleroma, Pleroma.Web.Endpoint,
Keyword.merge(endpoint_conf, http: [port: 4011], url: [port: 4011], server: true)}
]
})
*Note*: application configuration for a given key is not merged,
so any customization requires first fetching the existing values
and merging yourself by providing the merged configuration,
such as above with the endpoint config and repo config.
## Executing code within a remote node
Use the `within/2` macro to execute code within the context of a remote
federated node. The code block captures all local variable bindings from
the parent's context and returns the result of the expression after executing
it on the remote node. For example:
import Pleroma.Cluster
parent_value = 123
result =
within :"federated1@127.0.0.1" do
{node(), parent_value}
end
assert result == {:"federated1@127.0.0.1, 123}
*Note*: while local bindings are captured and available within the block,
other parent contexts like required, aliased, or imported modules are not
in scope. Those will need to be reimported/aliases/required within the block
as `within/2` is a remote procedure call.
"""
@extra_apps Pleroma.Mixfile.application()[:extra_applications]
@doc """
Spawns the default Pleroma federated cluster.
Values before may be customized as needed for the test suite.
"""
def spawn_default_cluster do
endpoint_conf = Application.fetch_env!(:pleroma, Pleroma.Web.Endpoint)
repo_conf = Application.fetch_env!(:pleroma, Pleroma.Repo)
spawn_cluster(%{
:"federated1@127.0.0.1" => [
{:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test_federated1")},
{:pleroma, Pleroma.Web.Endpoint,
Keyword.merge(endpoint_conf, http: [port: 4011], url: [port: 4011], server: true)}
],
:"federated2@127.0.0.1" => [
{:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test_federated2")},
{:pleroma, Pleroma.Web.Endpoint,
Keyword.merge(endpoint_conf, http: [port: 4012], url: [port: 4012], server: true)}
]
})
end
@doc """
Spawns a configured map of federated nodes.
See `Pleroma.Cluster` module documentation for details.
"""
def spawn_cluster(node_configs) do
# Turn node into a distributed node with the given long name
:net_kernel.start([:"primary@127.0.0.1"])
# Allow spawned nodes to fetch all code from this node
{:ok, _} = :erl_boot_server.start([])
allow_boot("127.0.0.1")
silence_logger_warnings(fn ->
node_configs
|> Enum.map(&Task.async(fn -> start_slave(&1) end))
|> Enum.map(&Task.await(&1, 60_000))
end)
end
@doc """
Executes block of code again remote node.
See `Pleroma.Cluster` module documentation for details.
"""
defmacro within(node, do: block) do
quote do
rpc(unquote(node), unquote(__MODULE__), :eval_quoted, [
unquote(Macro.escape(block)),
binding()
])
end
end
@doc false
def eval_quoted(block, binding) do
{result, _binding} = Code.eval_quoted(block, binding, __ENV__)
result
end
defp start_slave({node_host, override_configs}) do
log(node_host, "booting federated VM")
{:ok, node} = :slave.start(~c"127.0.0.1", node_name(node_host), vm_args())
add_code_paths(node)
load_apps_and_transfer_configuration(node, override_configs)
ensure_apps_started(node)
{:ok, node}
end
def rpc(node, module, function, args) do
:rpc.block_call(node, module, function, args)
end
defp vm_args do
~c"-loader inet -hosts 127.0.0.1 -setcookie #{:erlang.get_cookie()}"
end
defp allow_boot(host) do
{:ok, ipv4} = :inet.parse_ipv4_address(~c"#{host}")
:ok = :erl_boot_server.add_slave(ipv4)
end
defp add_code_paths(node) do
rpc(node, :code, :add_paths, [:code.get_path()])
end
defp load_apps_and_transfer_configuration(node, override_configs) do
Enum.each(Application.loaded_applications(), fn {app_name, _, _} ->
app_name
|> Application.get_all_env()
|> Enum.each(fn {key, primary_config} ->
rpc(node, Application, :put_env, [app_name, key, primary_config, [persistent: true]])
end)
end)
Enum.each(override_configs, fn {app_name, key, val} ->
rpc(node, Application, :put_env, [app_name, key, val, [persistent: true]])
end)
end
defp log(node, msg), do: IO.puts("[#{node}] #{msg}")
defp ensure_apps_started(node) do
loaded_names = Enum.map(Application.loaded_applications(), fn {name, _, _} -> name end)
app_names = @extra_apps ++ (loaded_names -- @extra_apps)
rpc(node, Application, :ensure_all_started, [:mix])
rpc(node, Mix, :env, [Mix.env()])
rpc(node, __MODULE__, :prepare_database, [])
log(node, "starting application")
Enum.reduce(app_names, MapSet.new(), fn app, loaded ->
if Enum.member?(loaded, app) do
loaded
else
{:ok, started} = rpc(node, Application, :ensure_all_started, [app])
MapSet.union(loaded, MapSet.new(started))
end
end)
end
@doc false
def prepare_database do
log(node(), "preparing database")
repo_config = Application.get_env(:pleroma, Pleroma.Repo)
repo_config[:adapter].storage_down(repo_config)
repo_config[:adapter].storage_up(repo_config)
{:ok, _, _} =
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
Ecto.Migrator.run(repo, :up, log: false, all: true)
end)
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
{:ok, _} = Application.ensure_all_started(:ex_machina)
end
defp silence_logger_warnings(func) do
prev_level = Logger.level()
Logger.configure(level: :error)
res = func.()
Logger.configure(level: prev_level)
res
end
defp node_name(node_host) do
node_host
|> to_string()
|> String.split("@")
|> Enum.at(0)
|> String.to_atom()
end
end

View File

@ -31,7 +31,6 @@ defmodule Pleroma.Factory do
nickname: sequence(:nickname, &"nick#{&1}"),
password_hash: Comeonin.Pbkdf2.hashpwsalt("test"),
bio: sequence(:bio, &"Tester Number #{&1}"),
info: %{},
last_digest_emailed_at: NaiveDateTime.utc_now()
}

View File

@ -1035,6 +1035,22 @@ defmodule HttpRequestMock do
}}
end
def get("http://localhost:8080/followers/fuser3", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/users_mock/friendica_followers.json")
}}
end
def get("http://localhost:8080/following/fuser3", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
body: File.read!("test/fixtures/users_mock/friendica_following.json")
}}
end
def get("http://localhost:4001/users/fuser2/followers", _, _, _) do
{:ok,
%Tesla.Env{

View File

@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
ExUnit.start(exclude: os_exclude)
ExUnit.start(exclude: [:federated | os_exclude])
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
{:ok, _} = Application.ensure_all_started(:ex_machina)

View File

@ -15,6 +15,14 @@ defmodule Pleroma.UserSearchTest do
end
describe "User.search" do
test "excluded invisible users from results" do
user = insert(:user, %{nickname: "john t1000"})
insert(:user, %{invisible: true, nickname: "john t800"})
[found_user] = User.search("john")
assert found_user.id == user.id
end
test "accepts limit parameter" do
Enum.each(0..4, &insert(:user, %{nickname: "john#{&1}"}))
assert length(User.search("john", limit: 3)) == 3

View File

@ -25,6 +25,25 @@ defmodule Pleroma.UserTest do
clear_config([:instance, :account_activation_required])
describe "service actors" do
test "returns invisible actor" do
uri = "#{Pleroma.Web.Endpoint.url()}/internal/fetch-test"
followers_uri = "#{uri}/followers"
user = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test")
assert %User{
nickname: "internal.fetch-test",
invisible: true,
local: true,
ap_id: ^uri,
follower_address: ^followers_uri
} = user
user2 = User.get_or_create_service_actor_by_ap_id(uri, "internal.fetch-test")
assert user.id == user2.id
end
end
describe "when tags are nil" do
test "tagging a user" do
user = insert(:user, %{tags: nil})
@ -148,9 +167,10 @@ defmodule Pleroma.UserTest do
{:ok, user} = User.follow(user, followed)
user = User.get_cached_by_id(user.id)
followed = User.get_cached_by_ap_id(followed.ap_id)
assert followed.follower_count == 1
assert user.following_count == 1
assert User.ap_followers(followed) in User.following(user)
end
@ -347,18 +367,6 @@ defmodule Pleroma.UserTest do
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
end
test "it ensures info is not nil" do
changeset = User.register_changeset(%User{}, @full_user_data)
assert changeset.valid?
{:ok, user} =
changeset
|> Repo.insert()
refute is_nil(user.info)
end
end
describe "user registration, with :account_activation_required" do
@ -412,8 +420,7 @@ defmodule Pleroma.UserTest do
:user,
local: false,
nickname: "admin@mastodon.example.org",
ap_id: ap_id,
info: %{}
ap_id: ap_id
)
{:ok, fetched_user} = User.get_or_fetch(ap_id)
@ -474,8 +481,7 @@ defmodule Pleroma.UserTest do
local: false,
nickname: "admin@mastodon.example.org",
ap_id: "http://mastodon.example.org/users/admin",
last_refreshed_at: a_week_ago,
info: %{}
last_refreshed_at: a_week_ago
)
assert orig_user.last_refreshed_at == a_week_ago
@ -516,7 +522,6 @@ defmodule Pleroma.UserTest do
name: "Someone",
nickname: "a@b.de",
ap_id: "http...",
info: %{some: "info"},
avatar: %{some: "avatar"}
}
@ -941,9 +946,9 @@ defmodule Pleroma.UserTest do
{:ok, user} = User.follow(user, user2)
{:ok, _user} = User.deactivate(user)
info = User.get_cached_user_info(user2)
user2 = User.get_cached_by_id(user2.id)
assert info.follower_count == 0
assert user2.follower_count == 0
assert [] = User.get_followers(user2)
end
@ -952,13 +957,15 @@ defmodule Pleroma.UserTest do
user2 = insert(:user)
{:ok, user2} = User.follow(user2, user)
assert user2.following_count == 1
assert User.following_count(user2) == 1
{:ok, _user} = User.deactivate(user)
info = User.get_cached_user_info(user2)
user2 = User.get_cached_by_id(user2.id)
assert info.following_count == 0
assert refresh_record(user2).following_count == 0
assert user2.following_count == 0
assert User.following_count(user2) == 0
assert [] = User.get_friends(user2)
end
@ -1121,8 +1128,7 @@ defmodule Pleroma.UserTest do
ap_id: user.ap_id,
name: user.name,
nickname: user.nickname,
bio: String.duplicate("h", current_max_length + 1),
info: %{}
bio: String.duplicate("h", current_max_length + 1)
}
assert {:ok, %User{}} = User.insert_or_update_user(data)
@ -1135,8 +1141,7 @@ defmodule Pleroma.UserTest do
data = %{
ap_id: user.ap_id,
name: String.duplicate("h", current_max_length + 1),
nickname: user.nickname,
info: %{}
nickname: user.nickname
}
assert {:ok, %User{}} = User.insert_or_update_user(data)
@ -1160,13 +1165,12 @@ defmodule Pleroma.UserTest do
describe "caching" do
test "invalidate_cache works" do
user = insert(:user)
_user_info = User.get_cached_user_info(user)
User.set_cache(user)
User.invalidate_cache(user)
{:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
{:ok, nil} = Cachex.get(:user_cache, "nickname:#{user.nickname}")
{:ok, nil} = Cachex.get(:user_cache, "user_info:#{user.id}")
end
test "User.delete() plugs any possible zombie objects" do
@ -1322,7 +1326,7 @@ defmodule Pleroma.UserTest do
{:ok, user} = User.block(user, follower)
assert User.user_info(user).follower_count == 2
assert user.follower_count == 2
end
describe "list_inactive_users_query/1" do
@ -1499,51 +1503,6 @@ defmodule Pleroma.UserTest do
end
end
describe "set_info_cache/2" do
setup do
user = insert(:user)
{:ok, user: user}
end
test "update from args", %{user: user} do
User.set_info_cache(user, %{following_count: 15, follower_count: 18})
%{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
assert followers == 18
assert following == 15
end
test "without args", %{user: user} do
User.set_info_cache(user, %{})
%{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
assert followers == 0
assert following == 0
end
end
describe "user_info/2" do
setup do
user = insert(:user)
{:ok, user: user}
end
test "update from args", %{user: user} do
%{follower_count: followers, following_count: following} =
User.user_info(user, %{following_count: 15, follower_count: 18})
assert followers == 18
assert following == 15
end
test "without args", %{user: user} do
%{follower_count: followers, following_count: following} = User.user_info(user)
assert followers == 0
assert following == 0
end
end
describe "is_internal_user?/1" do
test "non-internal user returns false" do
user = insert(:user)
@ -1600,14 +1559,14 @@ defmodule Pleroma.UserTest do
ap_enabled: true
)
assert User.user_info(other_user).following_count == 0
assert User.user_info(other_user).follower_count == 0
assert other_user.following_count == 0
assert other_user.follower_count == 0
{:ok, user} = Pleroma.User.follow(user, other_user)
other_user = Pleroma.User.get_by_id(other_user.id)
assert User.user_info(user).following_count == 1
assert User.user_info(other_user).follower_count == 1
assert user.following_count == 1
assert other_user.follower_count == 1
end
test "syncronizes the counters with the remote instance for the followed when enabled" do
@ -1623,14 +1582,14 @@ defmodule Pleroma.UserTest do
ap_enabled: true
)
assert User.user_info(other_user).following_count == 0
assert User.user_info(other_user).follower_count == 0
assert other_user.following_count == 0
assert other_user.follower_count == 0
Pleroma.Config.put([:instance, :external_user_synchronization], true)
{:ok, _user} = User.follow(user, other_user)
other_user = User.get_by_id(other_user.id)
assert User.user_info(other_user).follower_count == 437
assert other_user.follower_count == 437
end
test "syncronizes the counters with the remote instance for the follower when enabled" do
@ -1646,13 +1605,13 @@ defmodule Pleroma.UserTest do
ap_enabled: true
)
assert User.user_info(other_user).following_count == 0
assert User.user_info(other_user).follower_count == 0
assert other_user.following_count == 0
assert other_user.follower_count == 0
Pleroma.Config.put([:instance, :external_user_synchronization], true)
{:ok, other_user} = User.follow(other_user, user)
assert User.user_info(other_user).following_count == 152
assert other_user.following_count == 152
end
end

View File

@ -110,6 +110,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
end
test "it returns 404 for remote users", %{
conn: conn
} do
user = insert(:user, local: false, nickname: "remoteuser@example.com")
conn =
conn
|> put_req_header("accept", "application/json")
|> get("/users/#{user.nickname}.json")
assert json_response(conn, 404)
end
end
describe "/object/:uuid" do

View File

@ -4,8 +4,11 @@
defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
use Pleroma.DataCase
use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.Builders.ActivityBuilder
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@ -1554,5 +1557,80 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
assert follow_info.hide_followers == false
assert follow_info.hide_follows == true
end
test "detects hidden follows/followers for friendica" do
user =
insert(:user,
local: false,
follower_address: "http://localhost:8080/followers/fuser3",
following_address: "http://localhost:8080/following/fuser3"
)
{:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user)
assert follow_info.hide_followers == true
assert follow_info.follower_count == 296
assert follow_info.following_count == 32
assert follow_info.hide_follows == true
end
end
describe "Move activity" do
test "create" do
%{ap_id: old_ap_id} = old_user = insert(:user)
%{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
follower = insert(:user)
follower_move_opted_out = insert(:user, allow_following_move: false)
User.follow(follower, old_user)
User.follow(follower_move_opted_out, old_user)
assert User.following?(follower, old_user)
assert User.following?(follower_move_opted_out, old_user)
assert {:ok, activity} = ActivityPub.move(old_user, new_user)
assert %Activity{
actor: ^old_ap_id,
data: %{
"actor" => ^old_ap_id,
"object" => ^old_ap_id,
"target" => ^new_ap_id,
"type" => "Move"
},
local: true
} = activity
params = %{
"op" => "move_following",
"origin_id" => old_user.id,
"target_id" => new_user.id
}
assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params)
Pleroma.Workers.BackgroundWorker.perform(params, nil)
refute User.following?(follower, old_user)
assert User.following?(follower, new_user)
assert User.following?(follower_move_opted_out, old_user)
refute User.following?(follower_move_opted_out, new_user)
activity = %Activity{activity | object: nil}
assert [%Notification{activity: ^activity}] =
Notification.for_user_since(follower, ~N[2019-04-13 11:22:33])
assert [%Notification{activity: ^activity}] =
Notification.for_user_since(follower_move_opted_out, ~N[2019-04-13 11:22:33])
end
test "old user must be in the new user's `also_known_as` list" do
old_user = insert(:user)
new_user = insert(:user)
assert {:error, "Target account must have the origin in `alsoKnownAs`"} =
ActivityPub.move(old_user, new_user)
end
end
end

View File

@ -39,6 +39,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert activity == returned_activity
end
@tag capture_log: true
test "it fetches replied-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
@ -533,6 +534,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert object.data["content"] == "this is a private toot"
end
@tag capture_log: true
test "it rejects incoming announces with an inlined activity from another origin" do
data =
File.read!("test/fixtures/bogus-mastodon-announce.json")
@ -681,6 +683,37 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert user.bio == "<p>Some bio</p>"
end
test "it works with alsoKnownAs" do
{:ok, %Activity{data: %{"actor" => actor}}} =
"test/fixtures/mastodon-post-activity.json"
|> File.read!()
|> Poison.decode!()
|> Transmogrifier.handle_incoming()
assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"]
{:ok, _activity} =
"test/fixtures/mastodon-update.json"
|> File.read!()
|> Poison.decode!()
|> Map.put("actor", actor)
|> Map.update!("object", fn object ->
object
|> Map.put("actor", actor)
|> Map.put("id", actor)
|> Map.put("alsoKnownAs", [
"http://mastodon.example.org/users/foo",
"http://example.org/users/bar"
])
end)
|> Transmogrifier.handle_incoming()
assert User.get_cached_by_ap_id(actor).also_known_as == [
"http://mastodon.example.org/users/foo",
"http://example.org/users/bar"
]
end
test "it works with custom profile fields" do
{:ok, activity} =
"test/fixtures/mastodon-post-activity.json"
@ -814,6 +847,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert Activity.get_by_id(activity.id)
end
@tag capture_log: true
test "it works for incoming user deletes" do
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
@ -1269,6 +1303,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
assert [user.follower_address] == activity.data["to"]
end
test "it accepts Move activities" do
old_user = insert(:user)
new_user = insert(:user)
message = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Move",
"actor" => old_user.ap_id,
"object" => old_user.ap_id,
"target" => new_user.ap_id
}
assert :error = Transmogrifier.handle_incoming(message)
{:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]})
assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message)
assert activity.actor == old_user.ap_id
assert activity.data["actor"] == old_user.ap_id
assert activity.data["object"] == old_user.ap_id
assert activity.data["target"] == new_user.ap_id
assert activity.data["type"] == "Move"
end
end
describe "prepare outgoing" do
@ -1749,6 +1807,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert modified_object["inReplyToAtomUri"] == ""
end
@tag capture_log: true
test "returns modified object when allowed incoming reply", %{data: data} do
object_with_reply =
Map.put(
@ -1868,6 +1927,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
end) =~ "Unsupported URI scheme"
end
@tag capture_log: true
test "returns {:ok, %Object{}} for success case" do
assert {:ok, %Object{}} =
Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")

View File

@ -225,7 +225,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"roles" => %{"admin" => false, "moderator" => false},
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
assert expected == json_response(conn, 200)
@ -634,7 +635,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname)
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false
},
%{
"deactivated" => user.deactivated,
@ -644,7 +646,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => false,
"tags" => ["foo", "bar"],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
|> Enum.sort_by(& &1["nickname"])
@ -685,7 +688,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -709,7 +713,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -733,7 +738,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -757,7 +763,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -781,7 +788,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -805,7 +813,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -824,7 +833,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user2.name || user2.nickname)
"display_name" => HTML.strip_tags(user2.name || user2.nickname),
"confirmation_pending" => false
}
]
}
@ -853,7 +863,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -880,7 +891,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
},
%{
"deactivated" => admin.deactivated,
@ -890,7 +902,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname)
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false
},
%{
"deactivated" => false,
@ -900,7 +913,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"roles" => %{"admin" => true, "moderator" => false},
"tags" => [],
"avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname)
"display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
"confirmation_pending" => false
}
]
|> Enum.sort_by(& &1["nickname"])
@ -929,7 +943,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => admin.local,
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname)
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false
},
%{
"deactivated" => false,
@ -939,7 +954,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => second_admin.local,
"tags" => [],
"avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname)
"display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
"confirmation_pending" => false
}
]
|> Enum.sort_by(& &1["nickname"])
@ -970,7 +986,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => moderator.local,
"tags" => [],
"avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(moderator.name || moderator.nickname)
"display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
"confirmation_pending" => false
}
]
}
@ -994,7 +1011,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => user1.local,
"tags" => ["first"],
"avatar" => User.avatar_url(user1) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user1.name || user1.nickname)
"display_name" => HTML.strip_tags(user1.name || user1.nickname),
"confirmation_pending" => false
},
%{
"deactivated" => false,
@ -1004,7 +1022,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => user2.local,
"tags" => ["second"],
"avatar" => User.avatar_url(user2) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user2.name || user2.nickname)
"display_name" => HTML.strip_tags(user2.name || user2.nickname),
"confirmation_pending" => false
}
]
|> Enum.sort_by(& &1["nickname"])
@ -1040,7 +1059,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => user.local,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
]
}
@ -1066,7 +1086,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(admin) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(admin.name || admin.nickname)
"display_name" => HTML.strip_tags(admin.name || admin.nickname),
"confirmation_pending" => false
}
]
}
@ -1135,7 +1156,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"local" => true,
"tags" => [],
"avatar" => User.avatar_url(user) |> MediaProxy.url(),
"display_name" => HTML.strip_tags(user.name || user.nickname)
"display_name" => HTML.strip_tags(user.name || user.nickname),
"confirmation_pending" => false
}
log_entry = Repo.one(ModerationLog)
@ -1902,6 +1924,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
Pleroma.Config.put([:instance, :dynamic_configuration], true)
end
@tag capture_log: true
test "create new config setting in db", %{conn: conn} do
conn =
post(conn, "/api/pleroma/admin/config", %{
@ -2840,6 +2863,105 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
"@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
end
end
describe "instances" do
test "GET /instances/:instance/statuses" do
admin = insert(:user, is_admin: true)
user = insert(:user, local: false, nickname: "archaeme@archae.me")
user2 = insert(:user, local: false, nickname: "test@test.com")
insert_pair(:note_activity, user: user)
insert(:note_activity, user: user2)
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/instances/archae.me/statuses")
response = json_response(conn, 200)
assert length(response) == 2
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/instances/test.com/statuses")
response = json_response(conn, 200)
assert length(response) == 1
conn =
build_conn()
|> assign(:user, admin)
|> get("/api/pleroma/admin/instances/nonexistent.com/statuses")
response = json_response(conn, 200)
assert length(response) == 0
end
end
describe "PATCH /confirm_email" do
setup %{conn: conn} do
admin = insert(:user, is_admin: true)
%{conn: assign(conn, :user, admin), admin: admin}
end
test "it confirms emails of two users", %{admin: admin} do
[first_user, second_user] = insert_pair(:user, confirmation_pending: true)
assert first_user.confirmation_pending == true
assert second_user.confirmation_pending == true
build_conn()
|> assign(:user, admin)
|> patch("/api/pleroma/admin/users/confirm_email", %{
nicknames: [
first_user.nickname,
second_user.nickname
]
})
assert first_user.confirmation_pending == true
assert second_user.confirmation_pending == true
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{
second_user.nickname
}"
end
end
describe "PATCH /resend_confirmation_email" do
setup %{conn: conn} do
admin = insert(:user, is_admin: true)
%{conn: assign(conn, :user, admin), admin: admin}
end
test "it resend emails for two users", %{admin: admin} do
[first_user, second_user] = insert_pair(:user, confirmation_pending: true)
build_conn()
|> assign(:user, admin)
|> patch("/api/pleroma/admin/users/resend_confirmation_email", %{
nicknames: [
first_user.nickname,
second_user.nickname
]
})
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
second_user.nickname
}"
end
end
end
# Needed for testing

View File

@ -103,6 +103,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
assert user["locked"] == true
end
test "updates the user's allow_following_move", %{conn: conn} do
user = insert(:user)
assert user.allow_following_move == true
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
assert refresh_record(user).allow_following_move == false
assert user = json_response(conn, 200)
assert user["pleroma"]["allow_following_move"] == false
end
test "updates the user's default scope", %{conn: conn} do
user = insert(:user)

View File

@ -8,6 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
@ -118,6 +119,28 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
refute acc_one == acc_two
assert acc_two == acc_three
end
test "returns 404 when user is invisible", %{conn: conn} do
user = insert(:user, %{invisible: true})
resp =
conn
|> get("/api/v1/accounts/#{user.nickname}")
|> json_response(404)
assert %{"error" => "Can't find user"} = resp
end
test "returns 404 for internal.fetch actor", %{conn: conn} do
%User{nickname: "internal.fetch"} = InternalFetchActor.get_actor()
resp =
conn
|> get("/api/v1/accounts/internal.fetch")
|> json_response(404)
assert %{"error" => "Can't find user"} = resp
end
end
describe "user timelines" do

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do
use Pleroma.Web.ConnCase, async: true
use Pleroma.Web.ConnCase
alias Pleroma.Web.MastodonAPI.FilterView

View File

@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do
use Pleroma.Web.ConnCase, async: true
use Pleroma.Web.ConnCase
alias Pleroma.Repo
alias Pleroma.ScheduledActivity

View File

@ -102,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
privacy = user.default_scope
assert %{
pleroma: %{notification_settings: ^notification_settings},
pleroma: %{notification_settings: ^notification_settings, allow_following_move: true},
source: %{privacy: ^privacy}
} = AccountView.render("show.json", %{user: user, for: user})
end
@ -350,7 +350,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
}
}
assert expected == AccountView.render("show.json", %{user: user, for: other_user})
assert expected ==
AccountView.render("show.json", %{user: refresh_record(user), for: other_user})
end
test "returns the settings store if the requesting user is the represented user and it's requested specifically" do
@ -374,6 +375,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
refute result.display_name == "<marquee> username </marquee>"
end
test "never display nil user follow counts" do
user = insert(:user, following_count: 0, follower_count: 0)
result = AccountView.render("show.json", %{user: user})
assert result.following_count == 0
assert result.followers_count == 0
end
describe "hiding follows/following" do
test "shows when follows/followers stats are hidden and sets follow/follower count to 0" do
user =

View File

@ -107,4 +107,31 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
assert [] ==
NotificationView.render("index.json", %{notifications: [notification], for: followed})
end
test "Move notification" do
%{ap_id: old_ap_id} = old_user = insert(:user)
%{ap_id: _new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id])
follower = insert(:user)
User.follow(follower, old_user)
Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
Pleroma.Tests.ObanHelpers.perform_all()
old_user = refresh_record(old_user)
new_user = refresh_record(new_user)
[notification] = Notification.for_user(follower)
expected = %{
id: to_string(notification.id),
pleroma: %{is_seen: false},
type: "move",
account: AccountView.render("show.json", %{user: old_user, for: follower}),
target: AccountView.render("show.json", %{user: new_user, for: follower}),
created_at: Utils.to_masto_date(notification.inserted_at)
}
assert [expected] ==
NotificationView.render("index.json", %{notifications: [notification], for: follower})
end
end

View File

@ -35,23 +35,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
test "500s when user not found", %{conn: conn} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
url = "/objects/#{uuid}"
conn =
conn
|> put_req_header("accept", "application/xml")
|> get(url)
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "404s on private objects", %{conn: conn} do
note_activity = insert(:direct_note_activity)
object = Object.normalize(note_activity)
@ -82,21 +65,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert redirected_to(conn) == "/notice/#{note_activity.id}"
end
test "505s when user not found", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
user = User.get_cached_by_ap_id(note_activity.data["actor"])
User.invalidate_cache(user)
Pleroma.Repo.delete(user)
conn =
conn
|> put_req_header("accept", "text/html")
|> get("/activities/#{uuid}")
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "404s on private activities", %{conn: conn} do
note_activity = insert(:direct_note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
@ -127,21 +95,28 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
end
describe "GET notice/2" do
test "gets a notice in xml format", %{conn: conn} do
test "redirects to a proper object URL when json requested and the object is local", %{
conn: conn
} do
note_activity = insert(:note_activity)
expected_redirect_url = Object.normalize(note_activity).data["id"]
conn
|> get("/notice/#{note_activity.id}")
|> response(200)
redirect_url =
conn
|> put_req_header("accept", "application/activity+json")
|> get("/notice/#{note_activity.id}")
|> redirected_to()
assert redirect_url == expected_redirect_url
end
test "gets a notice in AS2 format", %{conn: conn} do
note_activity = insert(:note_activity)
test "returns a 404 on remote notice when json requested", %{conn: conn} do
note_activity = insert(:note_activity, local: false)
conn
|> put_req_header("accept", "application/activity+json")
|> get("/notice/#{note_activity.id}")
|> json_response(200)
|> response(404)
end
test "500s when actor not found", %{conn: conn} do
@ -157,32 +132,6 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
assert response(conn, 500) == ~S({"error":"Something went wrong"})
end
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
note_activity = insert(:note_activity)
url = "/notice/#{note_activity.id}"
conn =
conn
|> put_req_header("accept", "application/activity+json")
|> get(url)
assert json_response(conn, 200)
user = insert(:user)
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
url = "/notice/#{like_activity.id}"
assert like_activity.data["type"] == "Like"
conn =
build_conn()
|> put_req_header("accept", "application/activity+json")
|> get(url)
assert response(conn, 404)
end
test "render html for redirect for html format", %{conn: conn} do
note_activity = insert(:note_activity)

View File

@ -15,7 +15,7 @@ defmodule Pleroma.Web.StreamerTest do
alias Pleroma.Web.Streamer.StreamerSocket
alias Pleroma.Web.Streamer.Worker
@moduletag needs_streamer: true
@moduletag needs_streamer: true, capture_log: true
clear_config_all([:instance, :skip_thread_containment])
describe "user streams" do