Commit a1f7f9c6 authored by Roman Chvanikov's avatar Roman Chvanikov

Merge develop

parents e09f15b0 dc909081
Pipeline #20478 passed with stages
in 14 minutes and 4 seconds
......@@ -5,7 +5,6 @@ CC-BY-SA-4.0
COPYING
*file
elixir_buildpack.config
docs/
test/
# Required to get version
......
......@@ -30,6 +30,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Return `total` when querying for reports
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
- Admin API: Return link alongside with token on password reset
- Admin API: Support authentication via `x-admin-token` HTTP header
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
- Mastodon API: `pleroma.thread_muted` to the Status entity
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
......@@ -41,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.
<details>
......@@ -68,6 +70,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- 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
......@@ -78,8 +82,25 @@ 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
### Fixed
- Not being able to log into to third party apps when the browser is logged into mastofe
- Email confirmation not being required even when enabled
- Mastodon API: conversations API crashing when one status is malformed
### Bundled Pleroma-FE Changes
#### Added
- About page
- Meme arrows
#### Fixed
- Image modal not closing unless clicked outside of image
- Attachment upload spinner not being centered
- Showing follow counters being 0 when they are actually hidden
## [1.1.5] - 2019-11-09
### Fixed
- Polls having different numbers in timelines/notifications/poll api endpoints due to cache desyncronization
......
......@@ -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
......
......@@ -20,7 +20,7 @@ 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
# ## SSL Support
#
......
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"
......
......@@ -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: []
......
......@@ -235,14 +235,6 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
}
```
## DEPRECATED `PATCH /api/pleroma/admin/users/:nickname/activation_status`
### Active or deactivate a user
- Params:
- `nickname`
- `status` BOOLEAN field, false value means deactivation.
## `GET /api/pleroma/admin/users/:nickname_or_id`
### Retrive the details of a user
......@@ -878,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
......@@ -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>]
......
......@@ -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.
......@@ -656,7 +666,7 @@ Feel free to adjust the priv_dir and port number. Then you will have to create t
### :admin_token
Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example:
Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter or `x-admin-token` HTTP header. Example:
```elixir
config :pleroma, :admin_token, "somerandomtoken"
......@@ -664,8 +674,14 @@ config :pleroma, :admin_token, "somerandomtoken"
You can then do
```sh
curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken"
```shell
curl "http://localhost:4000/api/pleroma/admin/users/invites?admin_token=somerandomtoken"
```
or
```shell
curl -H "X-Admin-Token: somerandomtoken" "http://localhost:4000/api/pleroma/admin/users/invites"
```
### :auth
......
......@@ -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
......
......@@ -364,6 +364,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
......
......@@ -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
......
......@@ -101,7 +101,7 @@ 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]
......
......@@ -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
......
......@@ -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
......@@ -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}
......
......@@ -16,14 +16,28 @@ defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
def call(%{params: %{"admin_token" => admin_token}} = conn, _) do
if secret_token() && admin_token == secret_token() do
def call(conn, _) do
if secret_token() do
authenticate(conn)
else
conn
|> assign(:user, %User{is_admin: true})
end
end
def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
if admin_token == secret_token() do
assign(conn, :user, %User{is_admin: true})
else
conn
end
end
def call(conn, _), do: conn
def authenticate(conn) do
token = secret_token()
case get_req_header(conn, "x-admin-token") do
[^token] -> assign(conn, :user, %User{is_admin: true})
_ -> conn
end
end
end
......@@ -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)
......@@ -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
......@@ -177,19 +178,17 @@ defmodule Pleroma.User do
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))
following_count = Map.get(args, :following_count, user.following_count)
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
default_scope: user.default_scope,
follower_count: follower_count,
following_count: following_count
}
|> Map.put(:following_count, following_count)
|> Map.put(:follower_count, follower_count)
end
def follow_state(%User{} = user, %User{} = target) do
......@@ -492,6 +491,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 +525,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 +547,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 +559,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
......@@ -895,8 +894,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 +903,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 +1102,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
......@@ -1317,22 +1327,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 +1586,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
......
......@@ -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))
......
......@@ -785,6 +785,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> maybe_update_cc(list_memberships, opts["user"])
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
......@@ -1012,6 +1023,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
......@@ -1092,6 +1117,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)
......
......@@ -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
......
......@@ -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
......
......@@ -912,7 +912,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}}
......
......@@ -227,6 +227,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