Merge branch 'develop' into conversations-import

This commit is contained in:
Mark Felder 2019-05-16 13:11:17 -05:00
commit ebb0482116
18 changed files with 216 additions and 63 deletions

View File

@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- [Prometheus](https://prometheus.io/) metrics - [Prometheus](https://prometheus.io/) metrics
- Support for Mastodon's remote interaction - Support for Mastodon's remote interaction
- Mix Tasks: `mix pleroma.database remove_embedded_objects` - Mix Tasks: `mix pleroma.database remove_embedded_objects`
- Mix Tasks: `mix pleroma.user toggle_confirmed`
- Federation: Support for reports - Federation: Support for reports
- Configuration: `safe_dm_mentions` option - Configuration: `safe_dm_mentions` option
- Configuration: `link_name` option - Configuration: `link_name` option

View File

@ -106,15 +106,15 @@ Authentication is required and the user must be an admin.
- Method: `PUT` - Method: `PUT`
- Params: - Params:
- `nickname` - `nicknames` (array)
- `tags` - `tags` (array)
### Untag a list of users ### Untag a list of users
- Method: `DELETE` - Method: `DELETE`
- Params: - Params:
- `nickname` - `nicknames` (array)
- `tags` - `tags` (array)
## `/api/pleroma/admin/users/:nickname/permission_group` ## `/api/pleroma/admin/users/:nickname/permission_group`

View File

@ -12,6 +12,7 @@ This guide will assume you are on Debian Stretch. This guide should also work wi
* `erlang-tools` * `erlang-tools`
* `erlang-parsetools` * `erlang-parsetools`
* `erlang-eldap`, if you want to enable ldap authenticator * `erlang-eldap`, if you want to enable ldap authenticator
* `erlang-ssh`
* `erlang-xmerl` * `erlang-xmerl`
* `git` * `git`
* `build-essential` * `build-essential`
@ -49,7 +50,7 @@ sudo dpkg -i /tmp/erlang-solutions_1.0_all.deb
```shell ```shell
sudo apt update sudo apt update
sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools sudo apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
``` ```
### Install PleromaBE ### Install PleromaBE

View File

@ -14,6 +14,7 @@
- erlang-dev - erlang-dev
- erlang-tools - erlang-tools
- erlang-parsetools - erlang-parsetools
- erlang-ssh
- erlang-xmerl (Jessieではバックポートからインストールすること) - erlang-xmerl (Jessieではバックポートからインストールすること)
- git - git
- build-essential - build-essential
@ -44,7 +45,7 @@ wget -P /tmp/ https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
* ElixirとErlangをインストールします、 * ElixirとErlangをインストールします、
``` ```
apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools apt update && apt install elixir erlang-dev erlang-parsetools erlang-xmerl erlang-tools erlang-ssh
``` ```
### Pleroma BE (バックエンド) をインストールします ### Pleroma BE (バックエンド) をインストールします

View File

@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do
## Delete tags from a user. ## Delete tags from a user.
mix pleroma.user untag NICKNAME TAGS mix pleroma.user untag NICKNAME TAGS
## Toggle confirmation of the user's account.
mix pleroma.user toggle_confirmed NICKNAME
""" """
def run(["new", nickname, email | rest]) do def run(["new", nickname, email | rest]) do
{options, [], []} = {options, [], []} =
@ -388,6 +392,21 @@ def run(["delete_activities", nickname]) do
end end
end end
def run(["toggle_confirmed", nickname]) do
Common.start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.toggle_confirmation(user)
message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
Mix.shell().info("#{nickname} #{message} confirmation.")
else
_ ->
Mix.shell().error("No local user #{nickname}")
end
end
defp set_moderator(user, value) do defp set_moderator(user, value) do
info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value}) info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})

View File

@ -95,7 +95,6 @@ def handle_command(state, "home") do
activities = activities =
[user.ap_id | user.following] [user.ap_id | user.following]
|> ActivityPub.fetch_activities(params) |> ActivityPub.fetch_activities(params)
|> ActivityPub.contain_timeline(user)
Enum.each(activities, fn activity -> Enum.each(activities, fn activity ->
puts_activity(activity) puts_activity(activity)

View File

@ -38,7 +38,8 @@ def get_filters(%User{id: user_id} = _user) do
query = query =
from( from(
f in Pleroma.Filter, f in Pleroma.Filter,
where: f.user_id == ^user_id where: f.user_id == ^user_id,
order_by: [desc: :id]
) )
Repo.all(query) Repo.all(query)

View File

@ -1378,4 +1378,17 @@ def all_superusers do
def showing_reblogs?(%User{} = user, %User{} = target) do def showing_reblogs?(%User{} = user, %User{} = target) do
target.ap_id not in user.info.muted_reblogs target.ap_id not in user.info.muted_reblogs
end end
@spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
def toggle_confirmation(%User{} = user) do
need_confirmation? = !user.info.confirmation_pending
info_changeset =
User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
user
|> change()
|> put_embed(:info, info_changeset)
|> update_and_set_cache()
end
end end

View File

@ -212,7 +212,7 @@ def profile_update(info, params) do
]) ])
end end
@spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t() @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
def confirmation_changeset(info, opts) do def confirmation_changeset(info, opts) do
need_confirmation? = Keyword.get(opts, :need_confirmation) need_confirmation? = Keyword.get(opts, :need_confirmation)

View File

@ -527,17 +527,20 @@ def fetch_public_activities(opts \\ %{}) do
defp restrict_visibility(query, %{visibility: visibility}) defp restrict_visibility(query, %{visibility: visibility})
when is_list(visibility) do when is_list(visibility) do
if Enum.all?(visibility, &(&1 in @valid_visibilities)) do if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
from( query =
a in query, from(
where: a in query,
fragment( where:
"activity_visibility(?, ?, ?) = ANY (?)", fragment(
a.actor, "activity_visibility(?, ?, ?) = ANY (?)",
a.recipients, a.actor,
a.data, a.recipients,
^visibility a.data,
) ^visibility
) )
)
query
else else
Logger.error("Could not restrict visibility to #{visibility}") Logger.error("Could not restrict visibility to #{visibility}")
end end
@ -545,11 +548,14 @@ defp restrict_visibility(query, %{visibility: visibility})
defp restrict_visibility(query, %{visibility: visibility}) defp restrict_visibility(query, %{visibility: visibility})
when visibility in @valid_visibilities do when visibility in @valid_visibilities do
from( query =
a in query, from(
where: a in query,
fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility) where:
) fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)
)
query
end end
defp restrict_visibility(_query, %{visibility: visibility}) defp restrict_visibility(_query, %{visibility: visibility})
@ -559,6 +565,18 @@ defp restrict_visibility(_query, %{visibility: visibility})
defp restrict_visibility(query, _visibility), do: query defp restrict_visibility(query, _visibility), do: query
defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do
query =
from(
a in query,
where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data)
)
query
end
defp restrict_thread_visibility(query, _), do: query
def fetch_user_activities(user, reading_user, params \\ %{}) do def fetch_user_activities(user, reading_user, params \\ %{}) do
params = params =
params params
@ -838,6 +856,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_muted(opts) |> restrict_muted(opts)
|> restrict_media(opts) |> restrict_media(opts)
|> restrict_visibility(opts) |> restrict_visibility(opts)
|> restrict_thread_visibility(opts)
|> restrict_replies(opts) |> restrict_replies(opts)
|> restrict_reblogs(opts) |> restrict_reblogs(opts)
|> restrict_pinned(opts) |> restrict_pinned(opts)
@ -956,14 +975,6 @@ def contain_activity(%Activity{} = activity, %User{} = user) do
contain_broken_threads(activity, user) contain_broken_threads(activity, user)
end end
# do post-processing on a timeline
def contain_timeline(timeline, user) do
timeline
|> Enum.filter(fn activity ->
contain_activity(activity, user)
end)
end
def fetch_direct_messages_query do def fetch_direct_messages_query do
Activity Activity
|> restrict_type(%{"type" => "Create"}) |> restrict_type(%{"type" => "Create"})

View File

@ -1,6 +1,7 @@
defmodule Pleroma.Web.ActivityPub.Visibility do defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
@ -39,25 +40,14 @@ def visible_for_user?(activity, user) do
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y)) visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
end end
# guard def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
def entire_thread_visible_for_user?(nil, _user), do: false {:ok, %{rows: [[result]]}} =
Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [
user.ap_id,
activity.data["id"]
])
# XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop result
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
def entire_thread_visible_for_user?(
%Activity{} = tail,
# %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
user
) do
case Object.normalize(tail) do
%{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) ->
parent = Activity.get_in_reply_to_activity(tail)
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
_ ->
visible_for_user?(tail, user)
end
end end
def get_visibility(object) do def get_visibility(object) do

View File

@ -31,7 +31,7 @@ defmodule Pleroma.Web.Federator.Publisher do
""" """
@spec enqueue_one(module(), Map.t()) :: :ok @spec enqueue_one(module(), Map.t()) :: :ok
def enqueue_one(module, %{} = params), def enqueue_one(module, %{} = params),
do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params]) do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params])
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()} @spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
def perform(:publish_one, module, params) do def perform(:publish_one, module, params) do

View File

@ -303,7 +303,6 @@ def home_timeline(%{assigns: %{user: user}} = conn, params) do
activities = activities =
[user.ap_id | user.following] [user.ap_id | user.following]
|> ActivityPub.fetch_activities(params) |> ActivityPub.fetch_activities(params)
|> ActivityPub.contain_timeline(user)
|> Enum.reverse() |> Enum.reverse()
conn conn

View File

@ -101,9 +101,7 @@ def friends_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
|> Map.put("user", user) |> Map.put("user", user)
activities = activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
ActivityPub.fetch_activities([user.ap_id | user.following], params)
|> ActivityPub.contain_timeline(user)
conn conn
|> put_view(ActivityView) |> put_view(ActivityView)

View File

@ -0,0 +1,73 @@
defmodule Pleroma.Repo.Migrations.AddThreadVisibilityFunction do
use Ecto.Migration
@disable_ddl_transaction true
def up do
statement = """
CREATE OR REPLACE FUNCTION thread_visibility(actor varchar, activity_id varchar) RETURNS boolean AS $$
DECLARE
public varchar := 'https://www.w3.org/ns/activitystreams#Public';
child objects%ROWTYPE;
activity activities%ROWTYPE;
actor_user users%ROWTYPE;
author_fa varchar;
valid_recipients varchar[];
BEGIN
--- Fetch our actor.
SELECT * INTO actor_user FROM users WHERE users.ap_id = actor;
--- Fetch our initial activity.
SELECT * INTO activity FROM activities WHERE activities.data->>'id' = activity_id;
LOOP
--- Ensure that we have an activity before continuing.
--- If we don't, the thread is not satisfiable.
IF activity IS NULL THEN
RETURN false;
END IF;
--- We only care about Create activities.
IF activity.data->>'type' != 'Create' THEN
RETURN true;
END IF;
--- Normalize the child object into child.
SELECT * INTO child FROM objects
INNER JOIN activities ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
WHERE COALESCE(activity.data->'object'->>'id', activity.data->>'object') = objects.data->>'id';
--- Fetch the author's AS2 following collection.
SELECT COALESCE(users.follower_address, '') INTO author_fa FROM users WHERE users.ap_id = activity.actor;
--- Prepare valid recipients array.
valid_recipients := ARRAY[actor, public];
IF ARRAY[author_fa] && actor_user.following THEN
valid_recipients := valid_recipients || author_fa;
END IF;
--- Check visibility.
IF NOT valid_recipients && activity.recipients THEN
--- activity not visible, break out of the loop
RETURN false;
END IF;
--- If there's a parent, load it and do this all over again.
IF (child.data->'inReplyTo' IS NOT NULL) AND (child.data->'inReplyTo' != 'null'::jsonb) THEN
SELECT * INTO activity FROM activities
INNER JOIN objects ON COALESCE(activities.data->'object'->>'id', activities.data->>'object') = objects.data->>'id'
WHERE child.data->>'inReplyTo' = objects.data->>'id';
ELSE
RETURN true;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
"""
execute(statement)
end
def down do
execute("drop function thread_visibility(actor varchar, activity_id varchar)")
end
end

View File

@ -338,4 +338,31 @@ test "activities are deleted" do
assert message == "User #{nickname} statuses deleted." assert message == "User #{nickname} statuses deleted."
end end
end end
describe "running toggle_confirmed" do
test "user is confirmed" do
%{id: id, nickname: nickname} = insert(:user, info: %{confirmation_pending: false})
assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
assert_received {:mix_shell, :info, [message]}
assert message == "#{nickname} needs confirmation."
user = Repo.get(User, id)
assert user.info.confirmation_pending
assert user.info.confirmation_token
end
test "user is not confirmed" do
%{id: id, nickname: nickname} =
insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname])
assert_received {:mix_shell, :info, [message]}
assert message == "#{nickname} doesn't need confirmation."
user = Repo.get(User, id)
refute user.info.confirmation_pending
refute user.info.confirmation_token
end
end
end end

View File

@ -873,7 +873,6 @@ test "hide a user's statuses from timelines and notifications" do
assert [activity] == assert [activity] ==
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|> ActivityPub.contain_timeline(user2)
{:ok, _user} = User.deactivate(user) {:ok, _user} = User.deactivate(user)
@ -882,7 +881,6 @@ test "hide a user's statuses from timelines and notifications" do
assert [] == assert [] ==
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
|> ActivityPub.contain_timeline(user2)
end end
end end
@ -1204,4 +1202,22 @@ test "follower count is updated when a follower is blocked" do
assert Map.get(user_show, "followers_count") == 2 assert Map.get(user_show, "followers_count") == 2
end end
describe "toggle_confirmation/1" do
test "if user is confirmed" do
user = insert(:user, info: %{confirmation_pending: false})
{:ok, user} = User.toggle_confirmation(user)
assert user.info.confirmation_pending
assert user.info.confirmation_token
end
test "if user is unconfirmed" do
user = insert(:user, info: %{confirmation_pending: true, confirmation_token: "some token"})
{:ok, user} = User.toggle_confirmation(user)
refute user.info.confirmation_pending
refute user.info.confirmation_token
end
end
end end

View File

@ -960,17 +960,21 @@ test "it filters broken threads" do
"in_reply_to_status_id" => private_activity_2.id "in_reply_to_status_id" => private_activity_2.id
}) })
activities = ActivityPub.fetch_activities([user1.ap_id | user1.following]) activities =
ActivityPub.fetch_activities([user1.ap_id | user1.following])
|> Enum.map(fn a -> a.id end)
private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"]) private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
assert [public_activity, private_activity_1, private_activity_3] == activities assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
assert length(activities) == 3 assert length(activities) == 3
activities = ActivityPub.contain_timeline(activities, user1) activities =
ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
|> Enum.map(fn a -> a.id end)
assert [public_activity, private_activity_1] == activities assert [public_activity.id, private_activity_1.id] == activities
assert length(activities) == 2 assert length(activities) == 2
end end
end end