Merge remote-tracking branch 'pleroma/develop' into feature/disable-account
This commit is contained in:
commit
0f2f7d2cec
824 changed files with 14025 additions and 2958 deletions
|
|
@ -81,6 +81,14 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
|
||||
email = Common.get_option(options, :admin_email, "What is your admin email address?")
|
||||
|
||||
indexable =
|
||||
Common.get_option(
|
||||
options,
|
||||
:indexable,
|
||||
"Do you want search engines to index your site? (y/n)",
|
||||
"y"
|
||||
) === "y"
|
||||
|
||||
dbhost =
|
||||
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
||||
|
||||
|
|
@ -142,6 +150,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
Mix.shell().info("Writing #{psql_path}.")
|
||||
File.write(psql_path, result_psql)
|
||||
|
||||
write_robots_txt(indexable)
|
||||
|
||||
Mix.shell().info(
|
||||
"\n" <>
|
||||
"""
|
||||
|
|
@ -163,4 +173,28 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp write_robots_txt(indexable) do
|
||||
robots_txt =
|
||||
EEx.eval_file(
|
||||
Path.expand("robots_txt.eex", __DIR__),
|
||||
indexable: indexable
|
||||
)
|
||||
|
||||
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
|
||||
|
||||
unless File.exists?(static_dir) do
|
||||
File.mkdir_p!(static_dir)
|
||||
end
|
||||
|
||||
robots_txt_path = Path.join(static_dir, "robots.txt")
|
||||
|
||||
if File.exists?(robots_txt_path) do
|
||||
File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
|
||||
Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak")
|
||||
end
|
||||
|
||||
File.write(robots_txt_path, robots_txt)
|
||||
Mix.shell().info("Writing #{robots_txt_path}.")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Relay do
|
||||
use Mix.Task
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
@shortdoc "Manages remote relays"
|
||||
@moduledoc """
|
||||
|
|
|
|||
2
lib/mix/tasks/pleroma/robots_txt.eex
Normal file
2
lib/mix/tasks/pleroma/robots_txt.eex
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
User-Agent: *
|
||||
Disallow: <%= if indexable, do: "", else: "/" %>
|
||||
32
lib/mix/tasks/pleroma/robotstxt.ex
Normal file
32
lib/mix/tasks/pleroma/robotstxt.ex
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.RobotsTxt do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "Generate robots.txt"
|
||||
@moduledoc """
|
||||
Generates robots.txt
|
||||
|
||||
## Overwrite robots.txt to disallow all
|
||||
|
||||
mix pleroma.robots_txt disallow_all
|
||||
|
||||
This will write a robots.txt that will hide all paths on your instance
|
||||
from search engines and other robots that obey robots.txt
|
||||
|
||||
"""
|
||||
def run(["disallow_all"]) do
|
||||
static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/")
|
||||
|
||||
if !File.exists?(static_dir) do
|
||||
File.mkdir_p!(static_dir)
|
||||
end
|
||||
|
||||
robots_txt_path = Path.join(static_dir, "robots.txt")
|
||||
robots_txt_content = "User-Agent: *\nDisallow: /\n"
|
||||
|
||||
File.write!(robots_txt_path, robots_txt_content, [:write])
|
||||
end
|
||||
end
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Uploads do
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
alias Pleroma.Upload
|
||||
alias Pleroma.Uploaders.Local
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
require Logger
|
||||
|
||||
@log_every 50
|
||||
|
|
@ -20,7 +20,6 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|
|||
Options:
|
||||
- `--delete` - delete local uploads after migrating them to the target uploader
|
||||
|
||||
|
||||
A list of available uploaders can be seen in config.exs
|
||||
"""
|
||||
def run(["migrate_local", target_uploader | args]) do
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
defmodule Mix.Tasks.Pleroma.User do
|
||||
use Mix.Task
|
||||
import Ecto.Changeset
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
|
||||
@shortdoc "Manages Pleroma users"
|
||||
@moduledoc """
|
||||
|
|
@ -27,12 +27,28 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
## Generate an invite link.
|
||||
|
||||
mix pleroma.user invite
|
||||
mix pleroma.user invite [OPTION...]
|
||||
|
||||
Options:
|
||||
- `--expires_at DATE` - last day on which token is active (e.g. "2019-04-05")
|
||||
- `--max_use NUMBER` - maximum numbers of token uses
|
||||
|
||||
## List generated invites
|
||||
|
||||
mix pleroma.user invites
|
||||
|
||||
## Revoke invite
|
||||
|
||||
mix pleroma.user revoke_invite TOKEN OR TOKEN_ID
|
||||
|
||||
## Delete the user's account.
|
||||
|
||||
mix pleroma.user rm NICKNAME
|
||||
|
||||
## Delete the user's activities.
|
||||
|
||||
mix pleroma.user delete_activities NICKNAME
|
||||
|
||||
## Deactivate or activate the user's account.
|
||||
|
||||
mix pleroma.user toggle_activated NICKNAME
|
||||
|
|
@ -220,7 +236,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn friend ->
|
||||
user = Repo.get(User, user.id)
|
||||
user = User.get_by_id(user.id)
|
||||
|
||||
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||
User.unfollow(user, friend)
|
||||
|
|
@ -228,7 +244,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
:timer.sleep(500)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
user = User.get_by_id(user.id)
|
||||
|
||||
if Enum.empty?(user.following) do
|
||||
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||
|
|
@ -302,23 +318,91 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["invite"]) do
|
||||
def run(["invite" | rest]) do
|
||||
{options, [], []} =
|
||||
OptionParser.parse(rest,
|
||||
strict: [
|
||||
expires_at: :string,
|
||||
max_use: :integer
|
||||
]
|
||||
)
|
||||
|
||||
options =
|
||||
options
|
||||
|> Keyword.update(:expires_at, {:ok, nil}, fn
|
||||
nil -> {:ok, nil}
|
||||
val -> Date.from_iso8601(val)
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
Common.start_pleroma()
|
||||
|
||||
with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
|
||||
Mix.shell().info("Generated user invite token")
|
||||
with {:ok, val} <- options[:expires_at],
|
||||
options = Map.put(options, :expires_at, val),
|
||||
{:ok, invite} <- UserInviteToken.create_invite(options) do
|
||||
Mix.shell().info(
|
||||
"Generated user invite token " <> String.replace(invite.invite_type, "_", " ")
|
||||
)
|
||||
|
||||
url =
|
||||
Pleroma.Web.Router.Helpers.redirect_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:registration_page,
|
||||
token.token
|
||||
invite.token
|
||||
)
|
||||
|
||||
IO.puts(url)
|
||||
else
|
||||
error ->
|
||||
Mix.shell().error("Could not create invite token: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["invites"]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
Mix.shell().info("Invites list:")
|
||||
|
||||
UserInviteToken.list_invites()
|
||||
|> Enum.each(fn invite ->
|
||||
expire_info =
|
||||
with expires_at when not is_nil(expires_at) <- invite.expires_at do
|
||||
" | Expires at: #{Date.to_string(expires_at)}"
|
||||
end
|
||||
|
||||
using_info =
|
||||
with max_use when not is_nil(max_use) <- invite.max_use do
|
||||
" | Max use: #{max_use} Left use: #{max_use - invite.uses}"
|
||||
end
|
||||
|
||||
Mix.shell().info(
|
||||
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
|
||||
invite.used
|
||||
}#{expire_info}#{using_info}"
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
def run(["revoke_invite", token]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
with {:ok, invite} <- UserInviteToken.find_by_token(token),
|
||||
{:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do
|
||||
Mix.shell().info("Invite for token #{token} was revoked.")
|
||||
else
|
||||
_ -> Mix.shell().error("No invite found with token #{token}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete_activities", nickname]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
User.delete_user_activities(user)
|
||||
Mix.shell().info("User #{nickname} statuses deleted.")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("Could not create invite token.")
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ defmodule Pleroma.PasswordResetToken do
|
|||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.PasswordResetToken
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
schema "password_reset_tokens" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
|
|
@ -39,7 +39,7 @@ defmodule Pleroma.PasswordResetToken do
|
|||
|
||||
def reset_password(token, data) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- Repo.get(User, token.user_id),
|
||||
%User{} = user <- User.get_by_id(token.user_id),
|
||||
{:ok, _user} <- User.reset_password(user, data),
|
||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||
{:ok, token}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
defmodule Pleroma.Activity do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
|
|
@ -22,16 +23,53 @@ defmodule Pleroma.Activity do
|
|||
"Like" => "favourite"
|
||||
}
|
||||
|
||||
@mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types,
|
||||
into: %{},
|
||||
do: {v, k}
|
||||
|
||||
schema "activities" do
|
||||
field(:data, :map)
|
||||
field(:local, :boolean, default: true)
|
||||
field(:actor, :string)
|
||||
field(:recipients, {:array, :string})
|
||||
field(:recipients, {:array, :string}, default: [])
|
||||
has_many(:notifications, Notification, on_delete: :delete_all)
|
||||
|
||||
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
|
||||
# The foreign key is embedded in a jsonb field.
|
||||
#
|
||||
# To use it, you probably want to do an inner join and a preload:
|
||||
#
|
||||
# ```
|
||||
# |> join(:inner, [activity], o in Object,
|
||||
# on: fragment("(?->>'id') = COALESCE((?)->'object'->> 'id', (?)->>'object')",
|
||||
# o.data, activity.data, activity.data))
|
||||
# |> preload([activity, object], [object: object])
|
||||
# ```
|
||||
#
|
||||
# As a convenience, Activity.with_preloaded_object() sets up an inner join and preload for the
|
||||
# typical case.
|
||||
has_one(:object, Object, on_delete: :nothing, foreign_key: :id)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def with_preloaded_object(query) do
|
||||
query
|
||||
|> join(
|
||||
:inner,
|
||||
[activity],
|
||||
o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
)
|
||||
)
|
||||
|> preload([activity, object], object: object)
|
||||
end
|
||||
|
||||
def get_by_ap_id(ap_id) do
|
||||
Repo.one(
|
||||
from(
|
||||
|
|
@ -41,6 +79,24 @@ defmodule Pleroma.Activity do
|
|||
)
|
||||
end
|
||||
|
||||
def get_by_ap_id_with_object(ap_id) do
|
||||
Repo.one(
|
||||
from(
|
||||
activity in Activity,
|
||||
where: fragment("(?)->>'id' = ?", activity.data, ^to_string(ap_id)),
|
||||
left_join: o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
),
|
||||
preload: [object: o]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def get_by_id(id) do
|
||||
Activity
|
||||
|> where([a], a.id == ^id)
|
||||
|
|
@ -48,6 +104,22 @@ defmodule Pleroma.Activity do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
def get_by_id_with_object(id) do
|
||||
from(activity in Activity,
|
||||
where: activity.id == ^id,
|
||||
inner_join: o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
),
|
||||
preload: [object: o]
|
||||
)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def by_object_ap_id(ap_id) do
|
||||
from(
|
||||
activity in Activity,
|
||||
|
|
@ -75,7 +147,7 @@ defmodule Pleroma.Activity do
|
|||
)
|
||||
end
|
||||
|
||||
def create_by_object_ap_id(ap_id) do
|
||||
def create_by_object_ap_id(ap_id) when is_binary(ap_id) do
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
|
|
@ -89,6 +161,8 @@ defmodule Pleroma.Activity do
|
|||
)
|
||||
end
|
||||
|
||||
def create_by_object_ap_id(_), do: nil
|
||||
|
||||
def get_all_create_by_object_ap_id(ap_id) do
|
||||
Repo.all(create_by_object_ap_id(ap_id))
|
||||
end
|
||||
|
|
@ -101,8 +175,39 @@ defmodule Pleroma.Activity do
|
|||
|
||||
def get_create_by_object_ap_id(_), do: nil
|
||||
|
||||
def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
|
||||
def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
|
||||
def create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do
|
||||
from(
|
||||
activity in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
activity.data,
|
||||
activity.data,
|
||||
^to_string(ap_id)
|
||||
),
|
||||
where: fragment("(?)->>'type' = 'Create'", activity.data),
|
||||
inner_join: o in Object,
|
||||
on:
|
||||
fragment(
|
||||
"(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",
|
||||
o.data,
|
||||
activity.data,
|
||||
activity.data
|
||||
),
|
||||
preload: [object: o]
|
||||
)
|
||||
end
|
||||
|
||||
def create_by_object_ap_id_with_object(_), do: nil
|
||||
|
||||
def get_create_by_object_ap_id_with_object(ap_id) do
|
||||
ap_id
|
||||
|> create_by_object_ap_id_with_object()
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"])
|
||||
def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
||||
def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_id}}}) do
|
||||
|
|
@ -111,6 +216,19 @@ defmodule Pleroma.Activity do
|
|||
|
||||
def get_in_reply_to_activity(_), do: nil
|
||||
|
||||
def delete_by_ap_id(id) when is_binary(id) do
|
||||
by_object_ap_id(id)
|
||||
|> select([u], u)
|
||||
|> Repo.delete_all()
|
||||
|> elem(1)
|
||||
|> Enum.find(fn
|
||||
%{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id
|
||||
_ -> nil
|
||||
end)
|
||||
end
|
||||
|
||||
def delete_by_ap_id(_), do: nil
|
||||
|
||||
for {ap_type, type} <- @mastodon_notification_types do
|
||||
def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),
|
||||
do: unquote(type)
|
||||
|
|
@ -118,6 +236,10 @@ defmodule Pleroma.Activity do
|
|||
|
||||
def mastodon_notification_type(%Activity{}), do: nil
|
||||
|
||||
def from_mastodon_notification_type(type) do
|
||||
Map.get(@mastodon_to_ap_notification_types, type)
|
||||
end
|
||||
|
||||
def all_by_actor_and_id(actor, status_ids \\ [])
|
||||
def all_by_actor_and_id(_actor, []), do: []
|
||||
|
||||
|
|
@ -128,6 +250,52 @@ defmodule Pleroma.Activity do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
def increase_replies_count(id) do
|
||||
Activity
|
||||
|> where(id: ^id)
|
||||
|> update([a],
|
||||
set: [
|
||||
data:
|
||||
fragment(
|
||||
"""
|
||||
jsonb_set(?, '{object, repliesCount}',
|
||||
(coalesce((?->'object'->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true)
|
||||
""",
|
||||
a.data,
|
||||
a.data
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [activity]} -> activity
|
||||
_ -> {:error, "Not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def decrease_replies_count(id) do
|
||||
Activity
|
||||
|> where(id: ^id)
|
||||
|> update([a],
|
||||
set: [
|
||||
data:
|
||||
fragment(
|
||||
"""
|
||||
jsonb_set(?, '{object, repliesCount}',
|
||||
(greatest(0, (?->'object'->>'repliesCount')::int - 1))::varchar::jsonb, true)
|
||||
""",
|
||||
a.data,
|
||||
a.data
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [activity]} -> activity
|
||||
_ -> {:error, "Not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def restrict_disabled_users(query) do
|
||||
from(activity in query,
|
||||
where:
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ defmodule Pleroma.Application do
|
|||
@repository Mix.Project.config()[:source_url]
|
||||
def name, do: @name
|
||||
def version, do: @version
|
||||
def named_version(), do: @name <> " " <> @version
|
||||
def named_version, do: @name <> " " <> @version
|
||||
def repository, do: @repository
|
||||
|
||||
def user_agent() do
|
||||
def user_agent do
|
||||
info = "#{Pleroma.Web.base_url()} <#{Pleroma.Config.get([:instance, :email], "")}>"
|
||||
named_version() <> "; " <> info
|
||||
end
|
||||
|
|
@ -25,6 +25,7 @@ defmodule Pleroma.Application do
|
|||
import Cachex.Spec
|
||||
|
||||
Pleroma.Config.DeprecationWarnings.warn()
|
||||
setup_instrumenters()
|
||||
|
||||
# Define workers and child supervisors to be supervised
|
||||
children =
|
||||
|
|
@ -48,7 +49,7 @@ defmodule Pleroma.Application do
|
|||
[
|
||||
:user_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
default_ttl: 25_000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
|
|
@ -60,7 +61,7 @@ defmodule Pleroma.Application do
|
|||
[
|
||||
:object_cache,
|
||||
[
|
||||
default_ttl: 25000,
|
||||
default_ttl: 25_000,
|
||||
ttl_interval: 1000,
|
||||
limit: 2500
|
||||
]
|
||||
|
|
@ -103,15 +104,15 @@ defmodule Pleroma.Application do
|
|||
],
|
||||
id: :cachex_idem
|
||||
),
|
||||
worker(Pleroma.FlakeId, [])
|
||||
worker(Pleroma.FlakeId, []),
|
||||
worker(Pleroma.ScheduledActivityWorker, [])
|
||||
] ++
|
||||
hackney_pool_children() ++
|
||||
[
|
||||
worker(Pleroma.Web.Federator.RetryQueue, []),
|
||||
worker(Pleroma.Stats, []),
|
||||
worker(Pleroma.Web.Push, []),
|
||||
worker(Pleroma.Jobs, []),
|
||||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
|
||||
worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),
|
||||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init)
|
||||
] ++
|
||||
streamer_child() ++
|
||||
chat_child() ++
|
||||
|
|
@ -127,7 +128,25 @@ defmodule Pleroma.Application do
|
|||
Supervisor.start_link(children, opts)
|
||||
end
|
||||
|
||||
def enabled_hackney_pools() do
|
||||
defp setup_instrumenters do
|
||||
require Prometheus.Registry
|
||||
|
||||
:ok =
|
||||
:telemetry.attach(
|
||||
"prometheus-ecto",
|
||||
[:pleroma, :repo, :query],
|
||||
&Pleroma.Repo.Instrumenter.handle_event/4,
|
||||
%{}
|
||||
)
|
||||
|
||||
Prometheus.Registry.register_collector(:prometheus_process_collector)
|
||||
Pleroma.Web.Endpoint.MetricsExporter.setup()
|
||||
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
|
||||
Pleroma.Web.Endpoint.Instrumenter.setup()
|
||||
Pleroma.Repo.Instrumenter.setup()
|
||||
end
|
||||
|
||||
def enabled_hackney_pools do
|
||||
[:media] ++
|
||||
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
|
||||
[:federation]
|
||||
|
|
@ -142,14 +161,14 @@ defmodule Pleroma.Application do
|
|||
end
|
||||
|
||||
if Mix.env() == :test do
|
||||
defp streamer_child(), do: []
|
||||
defp chat_child(), do: []
|
||||
defp streamer_child, do: []
|
||||
defp chat_child, do: []
|
||||
else
|
||||
defp streamer_child() do
|
||||
defp streamer_child do
|
||||
[worker(Pleroma.Web.Streamer, [])]
|
||||
end
|
||||
|
||||
defp chat_child() do
|
||||
defp chat_child do
|
||||
if Pleroma.Config.get([:chat, :enabled]) do
|
||||
[worker(Pleroma.Web.ChatChannel.ChatChannelState, [])]
|
||||
else
|
||||
|
|
@ -158,7 +177,7 @@ defmodule Pleroma.Application do
|
|||
end
|
||||
end
|
||||
|
||||
defp hackney_pool_children() do
|
||||
defp hackney_pool_children do
|
||||
for pool <- enabled_hackney_pools() do
|
||||
options = Pleroma.Config.get([:hackney_pools, pool])
|
||||
:hackney_pool.child_spec(pool, options)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Captcha do
|
|||
use GenServer
|
||||
|
||||
@doc false
|
||||
def start_link() do
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ defmodule Pleroma.Captcha do
|
|||
@doc """
|
||||
Ask the configured captcha service for a new captcha
|
||||
"""
|
||||
def new() do
|
||||
def new do
|
||||
GenServer.call(__MODULE__, :new)
|
||||
end
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ defmodule Pleroma.Captcha do
|
|||
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
|
||||
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
|
||||
|
||||
# If the time found is less than (current_time - seconds_valid), then the time has already passed.
|
||||
# If the time found is less than (current_time-seconds_valid) then the time has already passed
|
||||
# Later we check that the time found is more than the presumed invalidatation time, that means
|
||||
# that the data is still valid and the captcha can be checked
|
||||
seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid])
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
|
|||
@behaviour Service
|
||||
|
||||
@impl Service
|
||||
def new() do
|
||||
def new do
|
||||
endpoint = Pleroma.Config.get!([__MODULE__, :endpoint])
|
||||
|
||||
case Tesla.get(endpoint <> "/new") do
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ defmodule Pleroma.Clippy do
|
|||
# No software is complete until they have a Clippy implementation.
|
||||
# A ballmer peak _may_ be required to change this module.
|
||||
|
||||
def tip() do
|
||||
def tip do
|
||||
tips()
|
||||
|> Enum.random()
|
||||
|> puts()
|
||||
end
|
||||
|
||||
def tips() do
|
||||
def tips do
|
||||
host = Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
|
||||
|
||||
[
|
||||
|
|
@ -92,8 +92,8 @@ defmodule Pleroma.Clippy do
|
|||
|
||||
# surrond one/five line clippy with blank lines around to not fuck up the layout
|
||||
#
|
||||
# yes this fix sucks but it's good enough, have you ever seen a release of windows wihtout some butched
|
||||
# features anyway?
|
||||
# yes this fix sucks but it's good enough, have you ever seen a release of windows
|
||||
# without some butched features anyway?
|
||||
lines =
|
||||
if length(lines) == 1 or length(lines) == 5 do
|
||||
[""] ++ lines ++ [""]
|
||||
|
|
|
|||
|
|
@ -57,4 +57,8 @@ defmodule Pleroma.Config do
|
|||
def delete(key) do
|
||||
Application.delete_env(:pleroma, key)
|
||||
end
|
||||
|
||||
def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
|
||||
|
||||
def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
defmodule Pleroma.Config.DeprecationWarnings do
|
||||
require Logger
|
||||
|
||||
def check_frontend_config_mechanism() do
|
||||
def check_frontend_config_mechanism do
|
||||
if Pleroma.Config.get(:fe) do
|
||||
Logger.warn("""
|
||||
!!!DEPRECATION WARNING!!!
|
||||
|
|
|
|||
|
|
@ -29,9 +29,13 @@ defmodule Pleroma.AdminEmail do
|
|||
if length(statuses) > 0 do
|
||||
statuses_list_html =
|
||||
statuses
|
||||
|> Enum.map(fn %{id: id} ->
|
||||
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
|
||||
"<li><a href=\"#{status_url}\">#{status_url}</li>"
|
||||
|> Enum.map(fn
|
||||
%{id: id} ->
|
||||
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id)
|
||||
"<li><a href=\"#{status_url}\">#{status_url}</li>"
|
||||
|
||||
id when is_binary(id) ->
|
||||
"<li><a href=\"#{id}\">#{id}</li>"
|
||||
end)
|
||||
|> Enum.join("\n")
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Mailer do
|
|||
use Swoosh.Mailer, otp_app: :pleroma
|
||||
|
||||
def deliver_async(email, config \\ []) do
|
||||
Pleroma.Jobs.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
||||
PleromaJobQueue.enqueue(:mailer, __MODULE__, [:deliver_async, email, config])
|
||||
end
|
||||
|
||||
def perform(:deliver_async, email, config), do: deliver(email, config)
|
||||
|
|
|
|||
|
|
@ -8,22 +8,28 @@ defmodule Pleroma.Emoji do
|
|||
|
||||
* the built-in Finmojis (if enabled in configuration),
|
||||
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
|
||||
* glob paths
|
||||
* glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
|
||||
|
||||
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
@type pattern :: Regex.t() | module() | String.t()
|
||||
@type patterns :: pattern() | [pattern()]
|
||||
@type group_patterns :: keyword(patterns())
|
||||
|
||||
@ets __MODULE__.Ets
|
||||
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
|
||||
@groups Application.get_env(:pleroma, :emoji)[:groups]
|
||||
|
||||
@doc false
|
||||
def start_link() do
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Reloads the emojis from disk."
|
||||
@spec reload() :: :ok
|
||||
def reload() do
|
||||
def reload do
|
||||
GenServer.call(__MODULE__, :reload)
|
||||
end
|
||||
|
||||
|
|
@ -38,7 +44,7 @@ defmodule Pleroma.Emoji do
|
|||
|
||||
@doc "Returns all the emojos!!"
|
||||
@spec get_all() :: [{String.t(), String.t()}, ...]
|
||||
def get_all() do
|
||||
def get_all do
|
||||
:ets.tab2list(@ets)
|
||||
end
|
||||
|
||||
|
|
@ -72,14 +78,15 @@ defmodule Pleroma.Emoji do
|
|||
{:ok, state}
|
||||
end
|
||||
|
||||
defp load() do
|
||||
defp load do
|
||||
finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
|
||||
shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
|
||||
|
||||
emojis =
|
||||
(load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
|
||||
(load_finmoji(finmoji_enabled) ++
|
||||
load_from_file("config/emoji.txt") ++
|
||||
load_from_file("config/custom_emoji.txt") ++
|
||||
load_from_globs(
|
||||
Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
|
||||
))
|
||||
load_from_globs(shortcode_globs))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
|
|
@ -151,9 +158,12 @@ defmodule Pleroma.Emoji do
|
|||
"white_nights",
|
||||
"woollysocks"
|
||||
]
|
||||
|
||||
defp load_finmoji(true) do
|
||||
Enum.map(@finmoji, fn finmoji ->
|
||||
{finmoji, "/finmoji/128px/#{finmoji}-128.png"}
|
||||
file_name = "/finmoji/128px/#{finmoji}-128.png"
|
||||
group = match_extra(@groups, file_name)
|
||||
{finmoji, file_name, to_string(group)}
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
@ -172,8 +182,14 @@ defmodule Pleroma.Emoji do
|
|||
|> Stream.map(&String.trim/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] -> {name, file}
|
||||
_ -> nil
|
||||
[name, file, tags] ->
|
||||
{name, file, tags}
|
||||
|
||||
[name, file] ->
|
||||
{name, file, to_string(match_extra(@groups, file))}
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end)
|
||||
|> Enum.to_list()
|
||||
|
|
@ -190,9 +206,40 @@ defmodule Pleroma.Emoji do
|
|||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path}
|
||||
{shortcode, external_path, to_string(tag)}
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Finds a matching group for the given emoji filename
|
||||
"""
|
||||
@spec match_extra(group_patterns(), String.t()) :: atom() | nil
|
||||
def match_extra(group_patterns, filename) do
|
||||
match_group_patterns(group_patterns, fn pattern ->
|
||||
case pattern do
|
||||
%Regex{} = regex -> Regex.match?(regex, filename)
|
||||
string when is_binary(string) -> filename == string
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp match_group_patterns(group_patterns, matcher) do
|
||||
Enum.find_value(group_patterns, fn {group, patterns} ->
|
||||
patterns =
|
||||
patterns
|
||||
|> List.wrap()
|
||||
|> Enum.map(fn pattern ->
|
||||
if String.contains?(pattern, "*") do
|
||||
~r(#{String.replace(pattern, "*", ".*")})
|
||||
else
|
||||
pattern
|
||||
end
|
||||
end)
|
||||
|
||||
Enum.any?(patterns, matcher) && group
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ defmodule Pleroma.Filter do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
schema "filters" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ defmodule Pleroma.FlakeId do
|
|||
|
||||
def from_string(string) when is_binary(string) and byte_size(string) < 18 do
|
||||
case Integer.parse(string) do
|
||||
{id, _} -> <<0::integer-size(64), id::integer-size(64)>>
|
||||
{id, ""} -> <<0::integer-size(64), id::integer-size(64)>>
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
|
@ -85,7 +85,7 @@ defmodule Pleroma.FlakeId do
|
|||
{:ok, FlakeId.from_string(value)}
|
||||
end
|
||||
|
||||
def autogenerate(), do: get()
|
||||
def autogenerate, do: get()
|
||||
|
||||
# -- GenServer API
|
||||
def start_link do
|
||||
|
|
@ -165,7 +165,7 @@ defmodule Pleroma.FlakeId do
|
|||
1_000_000_000 * mega_seconds + seconds * 1000 + :erlang.trunc(micro_seconds / 1000)
|
||||
end
|
||||
|
||||
defp worker_id() do
|
||||
defp worker_id do
|
||||
<<worker::integer-size(48)>> = :crypto.strong_rand_bytes(6)
|
||||
worker
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ defmodule Pleroma.Formatter do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
@safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/
|
||||
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/
|
||||
@link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui
|
||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
|
||||
@auto_linker_config hashtag: true,
|
||||
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
|
||||
|
|
@ -44,15 +46,28 @@ defmodule Pleroma.Formatter do
|
|||
|
||||
@doc """
|
||||
Parses a text and replace plain text links with HTML. Returns a tuple with a result text, mentions, and hashtags.
|
||||
|
||||
If the 'safe_mention' option is given, only consecutive mentions at the start the post are actually mentioned.
|
||||
"""
|
||||
@spec linkify(String.t(), keyword()) ::
|
||||
{String.t(), [{String.t(), User.t()}], [{String.t(), String.t()}]}
|
||||
def linkify(text, options \\ []) do
|
||||
options = options ++ @auto_linker_config
|
||||
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
|
||||
{text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
|
||||
|
||||
{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
|
||||
if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do
|
||||
%{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text)
|
||||
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
|
||||
|
||||
{text_mentions, %{mentions: mentions}} = AutoLinker.link_map(mentions, acc, options)
|
||||
{text_rest, %{tags: tags}} = AutoLinker.link_map(rest, acc, options)
|
||||
|
||||
{text_mentions <> text_rest, MapSet.to_list(mentions), MapSet.to_list(tags)}
|
||||
else
|
||||
acc = %{mentions: MapSet.new(), tags: MapSet.new()}
|
||||
{text, %{mentions: mentions, tags: tags}} = AutoLinker.link_map(text, acc, options)
|
||||
|
||||
{text, MapSet.to_list(mentions), MapSet.to_list(tags)}
|
||||
end
|
||||
end
|
||||
|
||||
def emojify(text) do
|
||||
|
|
@ -62,9 +77,9 @@ defmodule Pleroma.Formatter do
|
|||
def emojify(text, nil), do: text
|
||||
|
||||
def emojify(text, emoji, strip \\ false) do
|
||||
Enum.reduce(emoji, text, fn {emoji, file}, text ->
|
||||
emoji = HTML.strip_tags(emoji)
|
||||
file = HTML.strip_tags(file)
|
||||
Enum.reduce(emoji, text, fn emoji_data, text ->
|
||||
emoji = HTML.strip_tags(elem(emoji_data, 0))
|
||||
file = HTML.strip_tags(elem(emoji_data, 1))
|
||||
|
||||
html =
|
||||
if not strip do
|
||||
|
|
@ -86,7 +101,7 @@ defmodule Pleroma.Formatter do
|
|||
def demojify(text, nil), do: text
|
||||
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ defmodule Pleroma.Gopher.Server do
|
|||
use GenServer
|
||||
require Logger
|
||||
|
||||
def start_link() do
|
||||
def start_link do
|
||||
config = Pleroma.Config.get(:gopher, [])
|
||||
ip = Keyword.get(config, :ip, {0, 0, 0, 0})
|
||||
port = Keyword.get(config, :port, 1234)
|
||||
|
|
@ -36,11 +36,11 @@ defmodule Pleroma.Gopher.Server do
|
|||
end
|
||||
|
||||
defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
def start_link(ref, socket, transport, opts) do
|
||||
pid = spawn_link(__MODULE__, :init, [ref, socket, transport, opts])
|
||||
|
|
@ -65,7 +65,8 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
|
|||
def link(name, selector, type \\ 1) do
|
||||
address = Pleroma.Web.Endpoint.host()
|
||||
port = Pleroma.Config.get([:gopher, :port], 1234)
|
||||
"#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n"
|
||||
dstport = Pleroma.Config.get([:gopher, :dstport], port)
|
||||
"#{type}#{name}\t#{selector}\t#{address}\t#{dstport}\r\n"
|
||||
end
|
||||
|
||||
def render_activities(activities) do
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ defmodule Pleroma.HTML do
|
|||
defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers
|
||||
defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default]
|
||||
|
||||
def get_scrubbers() do
|
||||
def get_scrubbers do
|
||||
Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|> get_scrubbers
|
||||
end
|
||||
|
|
@ -28,27 +28,39 @@ defmodule Pleroma.HTML do
|
|||
def filter_tags(html), do: filter_tags(html, nil)
|
||||
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
|
||||
|
||||
def get_cached_scrubbed_html_for_object(content, scrubbers, object, module) do
|
||||
key = "#{module}#{generate_scrubber_signature(scrubbers)}|#{object.id}"
|
||||
Cachex.fetch!(:scrubber_cache, key, fn _key -> ensure_scrubbed_html(content, scrubbers) end)
|
||||
def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do
|
||||
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||
|
||||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false)
|
||||
end)
|
||||
end
|
||||
|
||||
def get_cached_stripped_html_for_object(content, object, module) do
|
||||
get_cached_scrubbed_html_for_object(
|
||||
def get_cached_stripped_html_for_activity(content, activity, key) do
|
||||
get_cached_scrubbed_html_for_activity(
|
||||
content,
|
||||
HtmlSanitizeEx.Scrubber.StripTags,
|
||||
object,
|
||||
module
|
||||
activity,
|
||||
key
|
||||
)
|
||||
end
|
||||
|
||||
def ensure_scrubbed_html(
|
||||
content,
|
||||
scrubbers
|
||||
scrubbers,
|
||||
false = _fake
|
||||
) do
|
||||
{:commit, filter_tags(content, scrubbers)}
|
||||
end
|
||||
|
||||
def ensure_scrubbed_html(
|
||||
content,
|
||||
scrubbers,
|
||||
true = _fake
|
||||
) do
|
||||
{:ignore, filter_tags(content, scrubbers)}
|
||||
end
|
||||
|
||||
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
|
||||
generate_scrubber_signature([scrubber])
|
||||
end
|
||||
|
|
@ -95,6 +107,13 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
|||
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
||||
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer"
|
||||
])
|
||||
|
||||
# paragraphs and linebreaks
|
||||
Meta.allow_tag_with_these_attributes("br", [])
|
||||
Meta.allow_tag_with_these_attributes("p", [])
|
||||
|
|
@ -137,6 +156,13 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
|
||||
Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
|
||||
|
||||
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
||||
"tag",
|
||||
"nofollow",
|
||||
"noopener",
|
||||
"noreferrer"
|
||||
])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("abbr", ["title"])
|
||||
|
||||
Meta.allow_tag_with_these_attributes("b", [])
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ defmodule Pleroma.HTTP.Connection do
|
|||
"""
|
||||
|
||||
@hackney_options [
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
connect_timeout: 2_000,
|
||||
recv_timeout: 20_000,
|
||||
follow_redirect: true,
|
||||
pool: :federation
|
||||
]
|
||||
|
|
@ -31,6 +31,10 @@ defmodule Pleroma.HTTP.Connection do
|
|||
#
|
||||
defp hackney_options(opts) do
|
||||
options = Keyword.get(opts, :adapter, [])
|
||||
@hackney_options ++ options
|
||||
adapter_options = Pleroma.Config.get([:http, :adapter], [])
|
||||
|
||||
@hackney_options
|
||||
|> Keyword.merge(adapter_options)
|
||||
|> Keyword.merge(options)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,21 +27,29 @@ defmodule Pleroma.HTTP do
|
|||
|
||||
"""
|
||||
def request(method, url, body \\ "", headers \\ [], options \\ []) do
|
||||
options =
|
||||
process_request_options(options)
|
||||
|> process_sni_options(url)
|
||||
try do
|
||||
options =
|
||||
process_request_options(options)
|
||||
|> process_sni_options(url)
|
||||
|
||||
params = Keyword.get(options, :params, [])
|
||||
params = Keyword.get(options, :params, [])
|
||||
|
||||
%{}
|
||||
|> Builder.method(method)
|
||||
|> Builder.headers(headers)
|
||||
|> Builder.opts(options)
|
||||
|> Builder.url(url)
|
||||
|> Builder.add_param(:body, :body, body)
|
||||
|> Builder.add_param(:query, :query, params)
|
||||
|> Enum.into([])
|
||||
|> (&Tesla.request(Connection.new(), &1)).()
|
||||
%{}
|
||||
|> Builder.method(method)
|
||||
|> Builder.headers(headers)
|
||||
|> Builder.opts(options)
|
||||
|> Builder.url(url)
|
||||
|> Builder.add_param(:body, :body, body)
|
||||
|> Builder.add_param(:query, :query, params)
|
||||
|> Enum.into([])
|
||||
|> (&Tesla.request(Connection.new(options), &1)).()
|
||||
rescue
|
||||
e ->
|
||||
{:error, e}
|
||||
catch
|
||||
:exit, e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
defp process_sni_options(options, nil), do: options
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ defmodule Pleroma.Instances.Instance do
|
|||
@moduledoc "Instance."
|
||||
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Repo
|
||||
|
||||
use Ecto.Schema
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ defmodule Pleroma.Instances.Instance do
|
|||
|
||||
schema "instances" do
|
||||
field(:host, :string)
|
||||
field(:unreachable_since, :naive_datetime)
|
||||
field(:unreachable_since, :naive_datetime_usec)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Jobs do
|
||||
@moduledoc """
|
||||
A basic job queue
|
||||
"""
|
||||
use GenServer
|
||||
|
||||
require Logger
|
||||
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
def start_link do
|
||||
queues =
|
||||
Pleroma.Config.get(Pleroma.Jobs)
|
||||
|> Enum.map(fn {name, _} -> create_queue(name) end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
state = %{
|
||||
queues: queues,
|
||||
refs: %{}
|
||||
}
|
||||
|
||||
GenServer.start_link(__MODULE__, state, name: __MODULE__)
|
||||
end
|
||||
|
||||
def create_queue(name) do
|
||||
{name, {:sets.new(), []}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enqueues a job.
|
||||
|
||||
Returns `:ok`.
|
||||
|
||||
## Arguments
|
||||
|
||||
- `queue_name` - a queue name(must be specified in the config).
|
||||
- `mod` - a worker module (must have `perform` function).
|
||||
- `args` - a list of arguments for the `perform` function of the worker module.
|
||||
- `priority` - a job priority (`0` by default).
|
||||
|
||||
## Examples
|
||||
|
||||
Enqueue `Module.perform/0` with `priority=1`:
|
||||
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [])
|
||||
:ok
|
||||
|
||||
Enqueue `Module.perform(:job_name)` with `priority=5`:
|
||||
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:job_name], 5)
|
||||
:ok
|
||||
|
||||
Enqueue `Module.perform(:another_job, data)` with `priority=1`:
|
||||
|
||||
iex> data = "foobar"
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:another_job, data])
|
||||
:ok
|
||||
|
||||
Enqueue `Module.perform(:foobar_job, :foo, :bar, 42)` with `priority=1`:
|
||||
|
||||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:foobar_job, :foo, :bar, 42])
|
||||
:ok
|
||||
|
||||
"""
|
||||
|
||||
def enqueue(queue_name, mod, args, priority \\ 1)
|
||||
|
||||
if Mix.env() == :test do
|
||||
def enqueue(_queue_name, mod, args, _priority) do
|
||||
apply(mod, :perform, args)
|
||||
end
|
||||
else
|
||||
@spec enqueue(atom(), atom(), [any()], integer()) :: :ok
|
||||
def enqueue(queue_name, mod, args, priority) do
|
||||
GenServer.cast(__MODULE__, {:enqueue, queue_name, mod, args, priority})
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast({:enqueue, queue_name, mod, args, priority}, state) do
|
||||
{running_jobs, queue} = state[:queues][queue_name]
|
||||
|
||||
queue = enqueue_sorted(queue, {mod, args}, priority)
|
||||
|
||||
state =
|
||||
state
|
||||
|> update_queue(queue_name, {running_jobs, queue})
|
||||
|> maybe_start_job(queue_name, running_jobs, queue)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do
|
||||
queue_name = state.refs[ref]
|
||||
|
||||
{running_jobs, queue} = state[:queues][queue_name]
|
||||
|
||||
running_jobs = :sets.del_element(ref, running_jobs)
|
||||
|
||||
state =
|
||||
state
|
||||
|> remove_ref(ref)
|
||||
|> update_queue(queue_name, {running_jobs, queue})
|
||||
|> maybe_start_job(queue_name, running_jobs, queue)
|
||||
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def maybe_start_job(state, queue_name, running_jobs, queue) do
|
||||
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, queue_name, :max_jobs]) &&
|
||||
queue != [] do
|
||||
{{mod, args}, queue} = queue_pop(queue)
|
||||
{:ok, pid} = Task.start(fn -> apply(mod, :perform, args) end)
|
||||
mref = Process.monitor(pid)
|
||||
|
||||
state
|
||||
|> add_ref(queue_name, mref)
|
||||
|> update_queue(queue_name, {:sets.add_element(mref, running_jobs), queue})
|
||||
else
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
def enqueue_sorted(queue, element, priority) do
|
||||
[%{item: element, priority: priority} | queue]
|
||||
|> Enum.sort_by(fn %{priority: priority} -> priority end)
|
||||
end
|
||||
|
||||
def queue_pop([%{item: element} | queue]) do
|
||||
{element, queue}
|
||||
end
|
||||
|
||||
defp add_ref(state, queue_name, ref) do
|
||||
refs = Map.put(state[:refs], ref, queue_name)
|
||||
Map.put(state, :refs, refs)
|
||||
end
|
||||
|
||||
defp remove_ref(state, ref) do
|
||||
refs = Map.delete(state[:refs], ref)
|
||||
Map.put(state, :refs, refs)
|
||||
end
|
||||
|
||||
defp update_queue(state, queue_name, data) do
|
||||
queues = Map.put(state[:queues], queue_name, data)
|
||||
Map.put(state, :queues, queues)
|
||||
end
|
||||
end
|
||||
|
|
@ -80,7 +80,7 @@ defmodule Pleroma.List do
|
|||
|
||||
# Get lists to which the account belongs.
|
||||
def get_lists_account_belongs(%User{} = owner, account_id) do
|
||||
user = Repo.get(User, account_id)
|
||||
user = User.get_by_id(account_id)
|
||||
|
||||
query =
|
||||
from(
|
||||
|
|
|
|||
|
|
@ -5,14 +5,17 @@
|
|||
defmodule Pleroma.Notification do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "notifications" do
|
||||
field(:seen, :boolean, default: false)
|
||||
|
|
@ -22,36 +25,37 @@ defmodule Pleroma.Notification do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
# TODO: Make generic and unify (see activity_pub.ex)
|
||||
defp restrict_max(query, %{"max_id" => max_id}) do
|
||||
from(activity in query, where: activity.id < ^max_id)
|
||||
def changeset(%Notification{} = notification, attrs) do
|
||||
notification
|
||||
|> cast(attrs, [:seen])
|
||||
end
|
||||
|
||||
defp restrict_max(query, _), do: query
|
||||
|
||||
defp restrict_since(query, %{"since_id" => since_id}) do
|
||||
from(activity in query, where: activity.id > ^since_id)
|
||||
end
|
||||
|
||||
defp restrict_since(query, _), do: query
|
||||
|
||||
def for_user(user, opts \\ %{}) do
|
||||
from(
|
||||
n in Notification,
|
||||
where: n.user_id == ^user.id,
|
||||
order_by: [desc: n.id],
|
||||
join: activity in assoc(n, :activity),
|
||||
preload: [activity: activity],
|
||||
limit: 20,
|
||||
where:
|
||||
def for_user_query(user) do
|
||||
Notification
|
||||
|> where(user_id: ^user.id)
|
||||
|> where(
|
||||
[n, a],
|
||||
fragment(
|
||||
"? not in (SELECT ap_id FROM users WHERE info->'disabled' @> 'true')",
|
||||
a.actor
|
||||
)
|
||||
)
|
||||
|> join(:inner, [n], activity in assoc(n, :activity))
|
||||
|> join(:left, [n, a], object in Object,
|
||||
on:
|
||||
fragment(
|
||||
"? not in (SELECT ap_id FROM users WHERE info->'disabled' @> 'true')",
|
||||
activity.actor
|
||||
"(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
|
||||
object.data,
|
||||
a.data
|
||||
)
|
||||
)
|
||||
|> restrict_since(opts)
|
||||
|> restrict_max(opts)
|
||||
|> Repo.all()
|
||||
|> preload([n, a, o], activity: {a, object: o})
|
||||
end
|
||||
|
||||
def for_user(user, opts \\ %{}) do
|
||||
user
|
||||
|> for_user_query()
|
||||
|> Pagination.fetch_paginated(opts)
|
||||
end
|
||||
|
||||
def set_read_up_to(%{id: user_id} = _user, id) do
|
||||
|
|
@ -68,6 +72,14 @@ defmodule Pleroma.Notification do
|
|||
Repo.update_all(query, [])
|
||||
end
|
||||
|
||||
def read_one(%User{} = user, notification_id) do
|
||||
with {:ok, %Notification{} = notification} <- get(user, notification_id) do
|
||||
notification
|
||||
|> changeset(%{seen: true})
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
||||
def get(%{id: user_id} = _user, id) do
|
||||
query =
|
||||
from(
|
||||
|
|
@ -117,13 +129,7 @@ defmodule Pleroma.Notification do
|
|||
|
||||
# TODO move to sql, too.
|
||||
def create_notification(%Activity{} = activity, %User{} = user) do
|
||||
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
|
||||
CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
|
||||
(activity.data["type"] == "Follow" and
|
||||
Enum.any?(Notification.for_user(user), fn notif ->
|
||||
notif.activity.data["type"] == "Follow" and
|
||||
notif.activity.data["actor"] == activity.data["actor"]
|
||||
end)) do
|
||||
unless skip?(activity, user) do
|
||||
notification = %Notification{user_id: user.id, activity: activity}
|
||||
{:ok, notification} = Repo.insert(notification)
|
||||
Pleroma.Web.Streamer.stream("user", notification)
|
||||
|
|
@ -143,10 +149,66 @@ defmodule Pleroma.Notification do
|
|||
[]
|
||||
|> 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)
|
||||
end
|
||||
|
||||
def get_notified_from_activity(_, _local_only), do: []
|
||||
|
||||
def skip?(activity, user) do
|
||||
[:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
|
||||
|> Enum.any?(&skip?(&1, activity, user))
|
||||
end
|
||||
|
||||
def skip?(:self, activity, user) do
|
||||
activity.data["actor"] == user.ap_id
|
||||
end
|
||||
|
||||
def skip?(:blocked, activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
User.blocks?(user, %{ap_id: actor})
|
||||
end
|
||||
|
||||
def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
|
||||
do: true
|
||||
|
||||
def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
|
||||
do: true
|
||||
|
||||
def skip?(:muted, activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
|
||||
User.mutes?(user, %{ap_id: actor}) or
|
||||
CommonAPI.thread_muted?(user, activity)
|
||||
end
|
||||
|
||||
def skip?(
|
||||
:followers,
|
||||
activity,
|
||||
%{info: %{notification_settings: %{"followers" => false}}} = user
|
||||
) do
|
||||
actor = activity.data["actor"]
|
||||
follower = User.get_cached_by_ap_id(actor)
|
||||
User.following?(follower, user)
|
||||
end
|
||||
|
||||
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
|
||||
actor = activity.data["actor"]
|
||||
followed = User.get_by_ap_id(actor)
|
||||
User.following?(user, followed)
|
||||
end
|
||||
|
||||
def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
|
||||
actor = activity.data["actor"]
|
||||
|
||||
Notification.for_user(user)
|
||||
|> Enum.any?(fn
|
||||
%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
|
||||
def skip?(_, _, _), do: false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,15 +5,17 @@
|
|||
defmodule Pleroma.Object do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.ObjectTombstone
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
require Logger
|
||||
|
||||
schema "objects" do
|
||||
field(:data, :map)
|
||||
|
||||
|
|
@ -38,6 +40,38 @@ defmodule Pleroma.Object do
|
|||
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
|
||||
end
|
||||
|
||||
# If we pass an Activity to Object.normalize(), we can try to use the preloaded object.
|
||||
# Use this whenever possible, especially when walking graphs in an O(N) loop!
|
||||
def normalize(%Activity{object: %Object{} = object}), do: object
|
||||
|
||||
# A hack for fake activities
|
||||
def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}) do
|
||||
%Object{id: "pleroma:fake_object_id", data: data}
|
||||
end
|
||||
|
||||
# Catch and log Object.normalize() calls where the Activity's child object is not
|
||||
# preloaded.
|
||||
def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do
|
||||
Logger.debug(
|
||||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
|
||||
)
|
||||
|
||||
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
||||
|
||||
normalize(ap_id)
|
||||
end
|
||||
|
||||
def normalize(%Activity{data: %{"object" => ap_id}}) do
|
||||
Logger.debug(
|
||||
"Object.normalize() called without preloaded object (#{ap_id}). Consider preloading the object!"
|
||||
)
|
||||
|
||||
Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}")
|
||||
|
||||
normalize(ap_id)
|
||||
end
|
||||
|
||||
# Old way, try fetching the object through cache.
|
||||
def normalize(%{"id" => ap_id}), do: normalize(ap_id)
|
||||
def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
|
@ -86,9 +120,9 @@ defmodule Pleroma.Object do
|
|||
|
||||
def delete(%Object{data: %{"id" => id}} = object) do
|
||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||
Repo.delete_all(Activity.by_object_ap_id(id)),
|
||||
deleted_activity = Activity.delete_by_ap_id(id),
|
||||
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
|
||||
{:ok, object}
|
||||
{:ok, object, deleted_activity}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -104,4 +138,50 @@ defmodule Pleroma.Object do
|
|||
e -> e
|
||||
end
|
||||
end
|
||||
|
||||
def increase_replies_count(ap_id) do
|
||||
Object
|
||||
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|
||||
|> update([o],
|
||||
set: [
|
||||
data:
|
||||
fragment(
|
||||
"""
|
||||
jsonb_set(?, '{repliesCount}',
|
||||
(coalesce((?->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true)
|
||||
""",
|
||||
o.data,
|
||||
o.data
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [object]} -> set_cache(object)
|
||||
_ -> {:error, "Not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def decrease_replies_count(ap_id) do
|
||||
Object
|
||||
|> where([o], fragment("?->>'id' = ?::text", o.data, ^to_string(ap_id)))
|
||||
|> update([o],
|
||||
set: [
|
||||
data:
|
||||
fragment(
|
||||
"""
|
||||
jsonb_set(?, '{repliesCount}',
|
||||
(greatest(0, (?->>'repliesCount')::int - 1))::varchar::jsonb, true)
|
||||
""",
|
||||
o.data,
|
||||
o.data
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [object]} -> set_cache(object)
|
||||
_ -> {:error, "Not found"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
78
lib/pleroma/pagination.ex
Normal file
78
lib/pleroma/pagination.ex
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
defmodule Pleroma.Pagination do
|
||||
@moduledoc """
|
||||
Implements Mastodon-compatible pagination.
|
||||
"""
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Repo
|
||||
|
||||
@default_limit 20
|
||||
|
||||
def fetch_paginated(query, params) do
|
||||
options = cast_params(params)
|
||||
|
||||
query
|
||||
|> paginate(options)
|
||||
|> Repo.all()
|
||||
|> enforce_order(options)
|
||||
end
|
||||
|
||||
def paginate(query, options) do
|
||||
query
|
||||
|> restrict(:min_id, options)
|
||||
|> restrict(:since_id, options)
|
||||
|> restrict(:max_id, options)
|
||||
|> restrict(:order, options)
|
||||
|> restrict(:limit, options)
|
||||
end
|
||||
|
||||
defp cast_params(params) do
|
||||
param_types = %{
|
||||
min_id: :string,
|
||||
since_id: :string,
|
||||
max_id: :string,
|
||||
limit: :integer
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
changeset.changes
|
||||
end
|
||||
|
||||
defp restrict(query, :min_id, %{min_id: min_id}) do
|
||||
where(query, [q], q.id > ^min_id)
|
||||
end
|
||||
|
||||
defp restrict(query, :since_id, %{since_id: since_id}) do
|
||||
where(query, [q], q.id > ^since_id)
|
||||
end
|
||||
|
||||
defp restrict(query, :max_id, %{max_id: max_id}) do
|
||||
where(query, [q], q.id < ^max_id)
|
||||
end
|
||||
|
||||
defp restrict(query, :order, %{min_id: _}) do
|
||||
order_by(query, [u], fragment("? asc nulls last", u.id))
|
||||
end
|
||||
|
||||
defp restrict(query, :order, _options) do
|
||||
order_by(query, [u], fragment("? desc nulls last", u.id))
|
||||
end
|
||||
|
||||
defp restrict(query, :limit, options) do
|
||||
limit = Map.get(options, :limit, @default_limit)
|
||||
|
||||
query
|
||||
|> limit(^limit)
|
||||
end
|
||||
|
||||
defp restrict(query, _, _), do: query
|
||||
|
||||
defp enforce_order(result, %{min_id: _}) do
|
||||
result
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
defp enforce_order(result, _), do: result
|
||||
end
|
||||
|
|
@ -34,13 +34,16 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
|
||||
defp csp_string do
|
||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||
websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
|
||||
static_url = Pleroma.Web.Endpoint.static_url()
|
||||
websocket_url = String.replace(static_url, "http", "ws")
|
||||
|
||||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
|
||||
connect_src =
|
||||
if Mix.env() == :dev do
|
||||
"connect-src 'self' http://localhost:3035/ " <> websocket_url
|
||||
connect_src <> " http://localhost:3035/"
|
||||
else
|
||||
"connect-src 'self' " <> websocket_url
|
||||
connect_src
|
||||
end
|
||||
|
||||
script_src =
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.HTTPSignatures
|
||||
import Plug.Conn
|
||||
require Logger
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ defmodule Pleroma.Plugs.InstanceStatic do
|
|||
end
|
||||
end
|
||||
|
||||
@only ~w(index.html static emoji packs sounds images instance favicon.png sw.js sw-pleroma.js)
|
||||
@only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js
|
||||
sw-pleroma.js)
|
||||
|
||||
def init(opts) do
|
||||
opts
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
import Plug.Conn
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
|
||||
|
|
@ -38,6 +38,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
preload: [user: user]
|
||||
)
|
||||
|
||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||
with %Token{user: %{info: %{deactivated: false} = _} = user} = token_record <- Repo.one(query) do
|
||||
{:ok, user, token_record}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,18 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
end
|
||||
|
||||
def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
|
||||
conn =
|
||||
case fetch_query_params(conn) do
|
||||
%{query_params: %{"name" => name}} = conn ->
|
||||
name = String.replace(name, "\"", "\\\"")
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-disposition", "filename=\"#{name}\"")
|
||||
|
||||
conn ->
|
||||
conn
|
||||
end
|
||||
|
||||
config = Pleroma.Config.get([Pleroma.Upload])
|
||||
|
||||
with uploader <- Keyword.fetch!(config, :uploader),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
defmodule Pleroma.Plugs.UserFetcherPlug do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Plug.Conn
|
||||
|
||||
def init(options) do
|
||||
|
|
@ -14,26 +12,10 @@ defmodule Pleroma.Plugs.UserFetcherPlug do
|
|||
|
||||
def call(conn, _options) do
|
||||
with %{auth_credentials: %{username: username}} <- conn.assigns,
|
||||
{:ok, %User{} = user} <- user_fetcher(username) do
|
||||
conn
|
||||
|> assign(:auth_user, user)
|
||||
%User{} = user <- User.get_by_nickname_or_email(username) do
|
||||
assign(conn, :auth_user, user)
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
defp user_fetcher(username_or_email) do
|
||||
{
|
||||
:ok,
|
||||
cond do
|
||||
# First, try logging in as if it was a name
|
||||
user = Repo.get_by(User, %{nickname: username_or_email}) ->
|
||||
user
|
||||
|
||||
# If we get nil, we try using it as an email
|
||||
user = Repo.get_by(User, %{email: username_or_email}) ->
|
||||
user
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
57
lib/pleroma/registration.ex
Normal file
57
lib/pleroma/registration.ex
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Registration do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
|
||||
schema "registrations" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
field(:provider, :string)
|
||||
field(:uid, :string)
|
||||
field(:info, :map, default: %{})
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def nickname(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "nickname", default)
|
||||
|
||||
def email(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "email", default)
|
||||
|
||||
def name(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "name", default)
|
||||
|
||||
def description(registration, default \\ nil),
|
||||
do: Map.get(registration.info, "description", default)
|
||||
|
||||
def changeset(registration, params \\ %{}) do
|
||||
registration
|
||||
|> cast(params, [:user_id, :provider, :uid, :info])
|
||||
|> validate_required([:provider, :uid])
|
||||
|> foreign_key_constraint(:user_id)
|
||||
|> unique_constraint(:uid, name: :registrations_provider_uid_index)
|
||||
end
|
||||
|
||||
def bind_to_user(registration, user) do
|
||||
registration
|
||||
|> changeset(%{user_id: (user && user.id) || nil})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def get_by_provider_uid(provider, uid) do
|
||||
Repo.get_by(Registration,
|
||||
provider: to_string(provider),
|
||||
uid: to_string(uid)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -3,7 +3,14 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Repo do
|
||||
use Ecto.Repo, otp_app: :pleroma
|
||||
use Ecto.Repo,
|
||||
otp_app: :pleroma,
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
migration_timestamps: [type: :naive_datetime_usec]
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.EctoInstrumenter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Dynamically loads the repository url from the
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReverseProxy do
|
||||
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since if-unmodified-since if-none-match if-range range)
|
||||
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
|
||||
~w(if-unmodified-since if-none-match if-range range)
|
||||
@resp_cache_headers ~w(etag date last-modified cache-control)
|
||||
@keep_resp_headers @resp_cache_headers ++
|
||||
~w(content-type content-disposition content-encoding content-range accept-ranges vary)
|
||||
~w(content-type content-disposition content-encoding content-range) ++
|
||||
~w(accept-ranges vary)
|
||||
@default_cache_control_header "public, max-age=1209600"
|
||||
@valid_resp_codes [200, 206, 304]
|
||||
@max_read_duration :timer.seconds(30)
|
||||
|
|
@ -282,8 +284,8 @@ defmodule Pleroma.ReverseProxy do
|
|||
headers
|
||||
|
||||
has_cache? ->
|
||||
# There's caching header present but no cache-control -- we need to explicitely override it to public
|
||||
# as Plug defaults to "max-age=0, private, must-revalidate"
|
||||
# There's caching header present but no cache-control -- we need to explicitely override it
|
||||
# to public as Plug defaults to "max-age=0, private, must-revalidate"
|
||||
List.keystore(headers, "cache-control", 0, {"cache-control", "public"})
|
||||
|
||||
true ->
|
||||
|
|
@ -309,7 +311,25 @@ defmodule Pleroma.ReverseProxy do
|
|||
end
|
||||
|
||||
if attachment? do
|
||||
disposition = "attachment; filename=" <> Keyword.get(opts, :attachment_name, "attachment")
|
||||
name =
|
||||
try do
|
||||
{{"content-disposition", content_disposition_string}, _} =
|
||||
List.keytake(headers, "content-disposition", 0)
|
||||
|
||||
[name | _] =
|
||||
Regex.run(
|
||||
~r/filename="((?:[^"\\]|\\.)*)"/u,
|
||||
content_disposition_string || "",
|
||||
capture: :all_but_first
|
||||
)
|
||||
|
||||
name
|
||||
rescue
|
||||
MatchError -> Keyword.get(opts, :attachment_name, "attachment")
|
||||
end
|
||||
|
||||
disposition = "attachment; filename=\"#{name}\""
|
||||
|
||||
List.keystore(headers, "content-disposition", 0, {"content-disposition", disposition})
|
||||
else
|
||||
headers
|
||||
|
|
|
|||
161
lib/pleroma/scheduled_activity.ex
Normal file
161
lib/pleroma/scheduled_activity.ex
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ScheduledActivity do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
@min_offset :timer.minutes(5)
|
||||
|
||||
schema "scheduled_activities" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
field(:scheduled_at, :naive_datetime)
|
||||
field(:params, :map)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||
scheduled_activity
|
||||
|> cast(attrs, [:scheduled_at, :params])
|
||||
|> validate_required([:scheduled_at, :params])
|
||||
|> validate_scheduled_at()
|
||||
|> with_media_attachments()
|
||||
end
|
||||
|
||||
defp with_media_attachments(
|
||||
%{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
|
||||
)
|
||||
when is_list(media_ids) do
|
||||
media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
|
||||
|
||||
params =
|
||||
params
|
||||
|> Map.put("media_attachments", media_attachments)
|
||||
|> Map.put("media_ids", media_ids)
|
||||
|
||||
put_change(changeset, :params, params)
|
||||
end
|
||||
|
||||
defp with_media_attachments(changeset), do: changeset
|
||||
|
||||
def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||
scheduled_activity
|
||||
|> cast(attrs, [:scheduled_at])
|
||||
|> validate_required([:scheduled_at])
|
||||
|> validate_scheduled_at()
|
||||
end
|
||||
|
||||
def validate_scheduled_at(changeset) do
|
||||
validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
|
||||
cond do
|
||||
not far_enough?(scheduled_at) ->
|
||||
[scheduled_at: "must be at least 5 minutes from now"]
|
||||
|
||||
exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
|
||||
[scheduled_at: "daily limit exceeded"]
|
||||
|
||||
exceeds_total_user_limit?(changeset.data.user_id) ->
|
||||
[scheduled_at: "total limit exceeded"]
|
||||
|
||||
true ->
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def exceeds_daily_user_limit?(user_id, scheduled_at) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user_id)
|
||||
|> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
|
||||
|> select([sa], count(sa.id))
|
||||
|> Repo.one()
|
||||
|> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
|
||||
end
|
||||
|
||||
def exceeds_total_user_limit?(user_id) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user_id)
|
||||
|> select([sa], count(sa.id))
|
||||
|> Repo.one()
|
||||
|> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
|
||||
end
|
||||
|
||||
def far_enough?(scheduled_at) when is_binary(scheduled_at) do
|
||||
with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
|
||||
far_enough?(scheduled_at)
|
||||
else
|
||||
_ -> false
|
||||
end
|
||||
end
|
||||
|
||||
def far_enough?(scheduled_at) do
|
||||
now = NaiveDateTime.utc_now()
|
||||
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
|
||||
diff > @min_offset
|
||||
end
|
||||
|
||||
def new(%User{} = user, attrs) do
|
||||
%ScheduledActivity{user_id: user.id}
|
||||
|> changeset(attrs)
|
||||
end
|
||||
|
||||
def create(%User{} = user, attrs) do
|
||||
user
|
||||
|> new(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def get(%User{} = user, scheduled_activity_id) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user.id)
|
||||
|> where(id: ^scheduled_activity_id)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def update(%ScheduledActivity{} = scheduled_activity, attrs) do
|
||||
scheduled_activity
|
||||
|> update_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def delete(%ScheduledActivity{} = scheduled_activity) do
|
||||
scheduled_activity
|
||||
|> Repo.delete()
|
||||
end
|
||||
|
||||
def delete(id) when is_binary(id) or is_integer(id) do
|
||||
ScheduledActivity
|
||||
|> where(id: ^id)
|
||||
|> select([sa], sa)
|
||||
|> Repo.delete_all()
|
||||
|> case do
|
||||
{1, [scheduled_activity]} -> {:ok, scheduled_activity}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def for_user_query(%User{} = user) do
|
||||
ScheduledActivity
|
||||
|> where(user_id: ^user.id)
|
||||
end
|
||||
|
||||
def due_activities(offset \\ 0) do
|
||||
naive_datetime =
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(offset, :millisecond)
|
||||
|
||||
ScheduledActivity
|
||||
|> where([sa], sa.scheduled_at < ^naive_datetime)
|
||||
|> Repo.all()
|
||||
end
|
||||
end
|
||||
58
lib/pleroma/scheduled_activity_worker.ex
Normal file
58
lib/pleroma/scheduled_activity_worker.ex
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ScheduledActivityWorker do
|
||||
@moduledoc """
|
||||
Sends scheduled activities to the job queue.
|
||||
"""
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
use GenServer
|
||||
require Logger
|
||||
|
||||
@schedule_interval :timer.minutes(1)
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, nil)
|
||||
end
|
||||
|
||||
def init(_) do
|
||||
if Config.get([ScheduledActivity, :enabled]) do
|
||||
schedule_next()
|
||||
{:ok, nil}
|
||||
else
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
def perform(:execute, scheduled_activity_id) do
|
||||
try do
|
||||
{:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
|
||||
%User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
|
||||
{:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
|
||||
rescue
|
||||
error ->
|
||||
Logger.error(
|
||||
"#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:perform, state) do
|
||||
ScheduledActivity.due_activities(@schedule_interval)
|
||||
|> Enum.each(fn scheduled_activity ->
|
||||
PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id])
|
||||
end)
|
||||
|
||||
schedule_next()
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp schedule_next do
|
||||
Process.send_after(self(), :perform, @schedule_interval)
|
||||
end
|
||||
end
|
||||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Stats do
|
||||
import Ecto.Query
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def start_link do
|
||||
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
defmodule Pleroma.ThreadMute do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.{Repo, User, ThreadMute}
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ThreadMute
|
||||
alias Pleroma.User
|
||||
|
||||
require Ecto.Query
|
||||
|
||||
schema "thread_mutes" do
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ defmodule Pleroma.Upload do
|
|||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => upload.content_type,
|
||||
"href" => url_from_spec(opts.base_url, url_spec)
|
||||
"href" => url_from_spec(upload, opts.base_url, url_spec)
|
||||
}
|
||||
],
|
||||
"name" => Map.get(opts, :description) || upload.name
|
||||
|
|
@ -85,6 +85,10 @@ defmodule Pleroma.Upload do
|
|||
end
|
||||
end
|
||||
|
||||
def char_unescaped?(char) do
|
||||
URI.char_unreserved?(char) or char == ?/
|
||||
end
|
||||
|
||||
defp get_opts(opts) do
|
||||
{size_limit, activity_type} =
|
||||
case Keyword.get(opts, :type) do
|
||||
|
|
@ -215,16 +219,18 @@ defmodule Pleroma.Upload do
|
|||
tmp_path
|
||||
end
|
||||
|
||||
defp url_from_spec(base_url, {:file, path}) do
|
||||
defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do
|
||||
path =
|
||||
path
|
||||
|> URI.encode()
|
||||
|> String.replace("?", "%3F")
|
||||
|> String.replace(":", "%3A")
|
||||
URI.encode(path, &char_unescaped?/1) <>
|
||||
if Pleroma.Config.get([__MODULE__, :link_name], false) do
|
||||
"?name=#{URI.encode(name, &char_unescaped?/1)}"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
[base_url, "media", path]
|
||||
|> Path.join()
|
||||
end
|
||||
|
||||
defp url_from_spec(_base_url, {:url, url}), do: url
|
||||
defp url_from_spec(_upload, _base_url, {:url, url}), do: url
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,16 +6,22 @@ defmodule Pleroma.Uploaders.S3 do
|
|||
@behaviour Pleroma.Uploaders.Uploader
|
||||
require Logger
|
||||
|
||||
# The file name is re-encoded with S3's constraints here to comply with previous links with less strict filenames
|
||||
# The file name is re-encoded with S3's constraints here to comply with previous
|
||||
# links with less strict filenames
|
||||
def get_file(file) do
|
||||
config = Pleroma.Config.get([__MODULE__])
|
||||
bucket = Keyword.fetch!(config, :bucket)
|
||||
|
||||
bucket_with_namespace =
|
||||
if namespace = Keyword.get(config, :bucket_namespace) do
|
||||
namespace <> ":" <> bucket
|
||||
else
|
||||
bucket
|
||||
cond do
|
||||
truncated_namespace = Keyword.get(config, :truncated_namespace) ->
|
||||
truncated_namespace
|
||||
|
||||
namespace = Keyword.get(config, :bucket_namespace) ->
|
||||
namespace <> ":" <> bucket
|
||||
|
||||
true ->
|
||||
bucket
|
||||
end
|
||||
|
||||
{:ok,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
|||
|> Poison.decode!()
|
||||
end
|
||||
|
||||
def get_token() do
|
||||
def get_token do
|
||||
settings = Pleroma.Config.get(Pleroma.Uploaders.Swift)
|
||||
username = Keyword.fetch!(settings, :username)
|
||||
password = Keyword.fetch!(settings, :password)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ defmodule Pleroma.Uploaders.Uploader do
|
|||
* `{:error, String.t}` error information if the file failed to be saved to the backend.
|
||||
* `:wait_callback` will wait for an http post request at `/api/pleroma/upload_callback/:upload_path` and call the uploader's `http_callback/3` method.
|
||||
|
||||
|
||||
"""
|
||||
@type file_spec :: {:file | :url, String.t()}
|
||||
@callback put_file(Pleroma.Upload.t()) ::
|
||||
|
|
|
|||
|
|
@ -8,21 +8,22 @@ defmodule Pleroma.User do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.Websub
|
||||
alias Pleroma.Web.OAuth
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
||||
alias Pleroma.Web.OAuth
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.RelMe
|
||||
alias Pleroma.Web.Websub
|
||||
|
||||
require Logger
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ defmodule Pleroma.User do
|
|||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
|
||||
# credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
|
||||
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
|
||||
|
||||
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
|
||||
|
|
@ -49,23 +51,21 @@ defmodule Pleroma.User do
|
|||
field(:local, :boolean, default: true)
|
||||
field(:follower_address, :string)
|
||||
field(:search_rank, :float, virtual: true)
|
||||
field(:search_type, :integer, virtual: true)
|
||||
field(:tags, {:array, :string}, default: [])
|
||||
field(:bookmarks, {:array, :string}, default: [])
|
||||
field(:last_refreshed_at, :naive_datetime)
|
||||
field(:last_refreshed_at, :naive_datetime_usec)
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
embeds_one(:info, Pleroma.User.Info)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def auth_active?(%User{local: false}), do: true
|
||||
|
||||
def auth_active?(%User{info: %User.Info{confirmation_pending: false}}), do: true
|
||||
|
||||
def auth_active?(%User{info: %User.Info{confirmation_pending: true}}),
|
||||
do: !Pleroma.Config.get([:instance, :account_activation_required])
|
||||
|
||||
def auth_active?(_), do: false
|
||||
def auth_active?(%User{}), do: true
|
||||
|
||||
def visible_for?(user, for_user \\ nil)
|
||||
|
||||
|
|
@ -81,17 +81,17 @@ defmodule Pleroma.User do
|
|||
def superuser?(%User{local: true, info: %User.Info{is_moderator: true}}), do: true
|
||||
def superuser?(_), do: false
|
||||
|
||||
def avatar_url(user) do
|
||||
def avatar_url(user, options \\ []) do
|
||||
case user.avatar do
|
||||
%{"url" => [%{"href" => href} | _]} -> href
|
||||
_ -> "#{Web.base_url()}/images/avi.png"
|
||||
_ -> !options[:no_default] && "#{Web.base_url()}/images/avi.png"
|
||||
end
|
||||
end
|
||||
|
||||
def banner_url(user) do
|
||||
def banner_url(user, options \\ []) do
|
||||
case user.info.banner do
|
||||
%{"url" => [%{"href" => href} | _]} -> href
|
||||
_ -> "#{Web.base_url()}/images/banner.png"
|
||||
_ -> !options[:no_default] && "#{Web.base_url()}/images/banner.png"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -103,9 +103,8 @@ defmodule Pleroma.User do
|
|||
"#{Web.base_url()}/users/#{nickname}"
|
||||
end
|
||||
|
||||
def ap_followers(%User{} = user) do
|
||||
"#{ap_id(user)}/followers"
|
||||
end
|
||||
def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
|
||||
def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
|
||||
|
||||
def user_info(%User{} = user) do
|
||||
%{
|
||||
|
|
@ -234,7 +233,7 @@ defmodule Pleroma.User do
|
|||
changeset =
|
||||
struct
|
||||
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:email, :name, :nickname, :password, :password_confirmation])
|
||||
|> validate_required([:name, :nickname, :password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> unique_constraint(:email)
|
||||
|> unique_constraint(:nickname)
|
||||
|
|
@ -245,6 +244,13 @@ defmodule Pleroma.User do
|
|||
|> validate_length(:name, min: 1, max: 100)
|
||||
|> put_change(:info, info_change)
|
||||
|
||||
changeset =
|
||||
if opts[:external] do
|
||||
changeset
|
||||
else
|
||||
validate_required(changeset, [:email])
|
||||
end
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||
|
|
@ -300,7 +306,7 @@ defmodule Pleroma.User do
|
|||
def needs_update?(%User{local: false, last_refreshed_at: nil}), do: true
|
||||
|
||||
def needs_update?(%User{local: false} = user) do
|
||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86400
|
||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), user.last_refreshed_at) >= 86_400
|
||||
end
|
||||
|
||||
def needs_update?(_), do: true
|
||||
|
|
@ -349,10 +355,11 @@ defmodule Pleroma.User do
|
|||
^followed_addresses
|
||||
)
|
||||
]
|
||||
]
|
||||
],
|
||||
select: u
|
||||
)
|
||||
|
||||
{1, [follower]} = Repo.update_all(q, [], returning: true)
|
||||
{1, [follower]} = Repo.update_all(q, [])
|
||||
|
||||
Enum.each(followeds, fn followed ->
|
||||
update_follower_count(followed)
|
||||
|
|
@ -382,10 +389,11 @@ defmodule Pleroma.User do
|
|||
q =
|
||||
from(u in User,
|
||||
where: u.id == ^follower.id,
|
||||
update: [push: [following: ^ap_followers]]
|
||||
update: [push: [following: ^ap_followers]],
|
||||
select: u
|
||||
)
|
||||
|
||||
{1, [follower]} = Repo.update_all(q, [], returning: true)
|
||||
{1, [follower]} = Repo.update_all(q, [])
|
||||
|
||||
{:ok, _} = update_follower_count(followed)
|
||||
|
||||
|
|
@ -400,10 +408,11 @@ defmodule Pleroma.User do
|
|||
q =
|
||||
from(u in User,
|
||||
where: u.id == ^follower.id,
|
||||
update: [pull: [following: ^ap_followers]]
|
||||
update: [pull: [following: ^ap_followers]],
|
||||
select: u
|
||||
)
|
||||
|
||||
{1, [follower]} = Repo.update_all(q, [], returning: true)
|
||||
{1, [follower]} = Repo.update_all(q, [])
|
||||
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
|
||||
|
|
@ -450,7 +459,8 @@ defmodule Pleroma.User do
|
|||
Repo.get_by(User, ap_id: ap_id)
|
||||
end
|
||||
|
||||
# This is mostly an SPC migration fix. This guesses the user nickname (by taking the last part of the ap_id and the domain) and tries to get that user
|
||||
# This is mostly an SPC migration fix. This guesses the user nickname by taking the last part
|
||||
# of the ap_id and the domain and tries to get that user
|
||||
def get_by_guessed_nickname(ap_id) do
|
||||
domain = URI.parse(ap_id).host
|
||||
name = List.last(String.split(ap_id, "/"))
|
||||
|
|
@ -519,11 +529,10 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def get_by_email(email), do: Repo.get_by(User, email: email)
|
||||
|
||||
def get_by_nickname_or_email(nickname_or_email) do
|
||||
case user = Repo.get_by(User, nickname: nickname_or_email) do
|
||||
%User{} -> user
|
||||
nil -> Repo.get_by(User, email: nickname_or_email)
|
||||
end
|
||||
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
|
||||
end
|
||||
|
||||
def get_cached_user_info(user) do
|
||||
|
|
@ -547,6 +556,10 @@ defmodule Pleroma.User do
|
|||
_e ->
|
||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
{:ok, user} <- fetch_by_nickname(nickname) do
|
||||
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
|
||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||
end
|
||||
|
||||
user
|
||||
else
|
||||
_e -> nil
|
||||
|
|
@ -554,6 +567,17 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "Fetch some posts when the user has just been federated with"
|
||||
def fetch_initial_posts(user) do
|
||||
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
|
||||
|
||||
Enum.each(
|
||||
# Insert all the posts in reverse order, so they're in the right order on the timeline
|
||||
Enum.reverse(Utils.fetch_ordered_collection(user.info.source_data["outbox"], pages)),
|
||||
&Pleroma.Web.Federator.incoming_ap_doc/1
|
||||
)
|
||||
end
|
||||
|
||||
def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do
|
||||
from(
|
||||
u in User,
|
||||
|
|
@ -637,7 +661,7 @@ defmodule Pleroma.User do
|
|||
users =
|
||||
user
|
||||
|> User.get_follow_requests_query()
|
||||
|> join(:inner, [a], u in User, a.actor == u.ap_id)
|
||||
|> join(:inner, [a], u in User, on: a.actor == u.ap_id)
|
||||
|> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
|
||||
|> group_by([a, u], u.id)
|
||||
|> select([a, u], u)
|
||||
|
|
@ -659,7 +683,8 @@ defmodule Pleroma.User do
|
|||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([], returning: true)
|
||||
|> select([u], u)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [user]} -> set_cache(user)
|
||||
_ -> {:error, user}
|
||||
|
|
@ -679,7 +704,8 @@ defmodule Pleroma.User do
|
|||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([], returning: true)
|
||||
|> select([u], u)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [user]} -> set_cache(user)
|
||||
_ -> {:error, user}
|
||||
|
|
@ -725,7 +751,8 @@ defmodule Pleroma.User do
|
|||
)
|
||||
]
|
||||
)
|
||||
|> Repo.update_all([], returning: true)
|
||||
|> select([u], u)
|
||||
|> Repo.update_all([])
|
||||
|> case do
|
||||
{1, [user]} -> set_cache(user)
|
||||
_ -> {:error, user}
|
||||
|
|
@ -766,77 +793,59 @@ defmodule Pleroma.User do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
@spec search_for_admin(binary(), %{
|
||||
admin: Pleroma.User.t(),
|
||||
local: boolean(),
|
||||
page: number(),
|
||||
page_size: number()
|
||||
}) :: {:ok, [Pleroma.User.t()], number()}
|
||||
def search_for_admin(term, %{admin: admin, local: local, page: page, page_size: page_size}) do
|
||||
term = String.trim_leading(term, "@")
|
||||
|
||||
local_paginated_query =
|
||||
User
|
||||
|> maybe_local_user_query(local)
|
||||
|> paginate(page, page_size)
|
||||
|
||||
search_query = fts_search_subquery(term, local_paginated_query)
|
||||
|
||||
count =
|
||||
term
|
||||
|> fts_search_subquery()
|
||||
|> maybe_local_user_query(local)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
{:ok, do_search(search_query, admin), count}
|
||||
end
|
||||
|
||||
@spec all_for_admin(number(), number()) :: {:ok, [Pleroma.User.t()], number()}
|
||||
def all_for_admin(page, page_size) do
|
||||
query = from(u in User, order_by: u.id)
|
||||
|
||||
paginated_query =
|
||||
query
|
||||
|> paginate(page, page_size)
|
||||
|
||||
count =
|
||||
query
|
||||
|> Repo.aggregate(:count, :id)
|
||||
|
||||
{:ok, Repo.all(paginated_query), count}
|
||||
end
|
||||
|
||||
def search(query, resolve \\ false, for_user \\ nil) do
|
||||
# Strip the beginning @ off if there is a query
|
||||
query = String.trim_leading(query, "@")
|
||||
|
||||
if resolve, do: get_or_fetch(query)
|
||||
|
||||
fts_results = do_search(fts_search_subquery(query), for_user)
|
||||
|
||||
{:ok, trigram_results} =
|
||||
{:ok, results} =
|
||||
Repo.transaction(fn ->
|
||||
Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
|
||||
do_search(trigram_search_subquery(query), for_user)
|
||||
Repo.all(search_query(query, for_user))
|
||||
end)
|
||||
|
||||
Enum.uniq_by(fts_results ++ trigram_results, & &1.id)
|
||||
results
|
||||
end
|
||||
|
||||
defp do_search(subquery, for_user, options \\ []) do
|
||||
q =
|
||||
from(
|
||||
s in subquery(subquery),
|
||||
order_by: [desc: s.search_rank],
|
||||
limit: ^(options[:limit] || 20)
|
||||
)
|
||||
def search_query(query, for_user) do
|
||||
fts_subquery = fts_search_subquery(query)
|
||||
trigram_subquery = trigram_search_subquery(query)
|
||||
union_query = from(s in trigram_subquery, union_all: ^fts_subquery)
|
||||
distinct_query = from(s in subquery(union_query), order_by: s.search_type, distinct: s.id)
|
||||
|
||||
results =
|
||||
q
|
||||
|> Repo.all()
|
||||
|> Enum.filter(&(&1.search_rank > 0))
|
||||
from(s in subquery(boost_search_rank_query(distinct_query, for_user)),
|
||||
order_by: [desc: s.search_rank],
|
||||
limit: 20
|
||||
)
|
||||
end
|
||||
|
||||
boost_search_results(results, for_user)
|
||||
defp boost_search_rank_query(query, nil), do: query
|
||||
|
||||
defp boost_search_rank_query(query, for_user) do
|
||||
friends_ids = get_friends_ids(for_user)
|
||||
followers_ids = get_followers_ids(for_user)
|
||||
|
||||
from(u in subquery(query),
|
||||
select_merge: %{
|
||||
search_rank:
|
||||
fragment(
|
||||
"""
|
||||
CASE WHEN (?) THEN (?) * 1.3
|
||||
WHEN (?) THEN (?) * 1.2
|
||||
WHEN (?) THEN (?) * 1.1
|
||||
ELSE (?) END
|
||||
""",
|
||||
u.id in ^friends_ids and u.id in ^followers_ids,
|
||||
u.search_rank,
|
||||
u.id in ^friends_ids,
|
||||
u.search_rank,
|
||||
u.id in ^followers_ids,
|
||||
u.search_rank,
|
||||
u.search_rank
|
||||
)
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
defp fts_search_subquery(term, query \\ User) do
|
||||
|
|
@ -851,6 +860,7 @@ defmodule Pleroma.User do
|
|||
from(
|
||||
u in query,
|
||||
select_merge: %{
|
||||
search_type: ^0,
|
||||
search_rank:
|
||||
fragment(
|
||||
"""
|
||||
|
|
@ -884,6 +894,8 @@ defmodule Pleroma.User do
|
|||
from(
|
||||
u in User,
|
||||
select_merge: %{
|
||||
# ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
|
||||
search_type: fragment("?", 1),
|
||||
search_rank:
|
||||
fragment(
|
||||
"similarity(?, trim(? || ' ' || coalesce(?, '')))",
|
||||
|
|
@ -897,33 +909,6 @@ defmodule Pleroma.User do
|
|||
|> restrict_disabled()
|
||||
end
|
||||
|
||||
defp boost_search_results(results, nil), do: results
|
||||
|
||||
defp boost_search_results(results, for_user) do
|
||||
friends_ids = get_friends_ids(for_user)
|
||||
followers_ids = get_followers_ids(for_user)
|
||||
|
||||
Enum.map(
|
||||
results,
|
||||
fn u ->
|
||||
search_rank_coef =
|
||||
cond do
|
||||
u.id in friends_ids ->
|
||||
1.2
|
||||
|
||||
u.id in followers_ids ->
|
||||
1.1
|
||||
|
||||
true ->
|
||||
1
|
||||
end
|
||||
|
||||
Map.put(u, :search_rank, u.search_rank * search_rank_coef)
|
||||
end
|
||||
)
|
||||
|> Enum.sort_by(&(-&1.search_rank))
|
||||
end
|
||||
|
||||
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
|
||||
Enum.map(
|
||||
blocked_identifiers,
|
||||
|
|
@ -965,6 +950,38 @@ defmodule Pleroma.User do
|
|||
update_and_set_cache(cng)
|
||||
end
|
||||
|
||||
def subscribe(subscriber, %{ap_id: ap_id}) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do
|
||||
blocked = blocks?(subscribed, subscriber) and deny_follow_blocked
|
||||
|
||||
if blocked do
|
||||
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"}
|
||||
else
|
||||
info_cng =
|
||||
subscribed.info
|
||||
|> User.Info.add_to_subscribers(subscriber.ap_id)
|
||||
|
||||
change(subscribed)
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do
|
||||
with %User{} = user <- get_cached_by_ap_id(ap_id) do
|
||||
info_cng =
|
||||
user.info
|
||||
|> User.Info.remove_from_subscribers(unsubscriber.ap_id)
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info_cng)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
end
|
||||
|
||||
def block(blocker, %User{ap_id: ap_id} = blocked) do
|
||||
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
|
||||
blocker =
|
||||
|
|
@ -975,10 +992,20 @@ defmodule Pleroma.User do
|
|||
blocker
|
||||
end
|
||||
|
||||
blocker =
|
||||
if subscribed_to?(blocked, blocker) do
|
||||
{:ok, blocker} = unsubscribe(blocked, blocker)
|
||||
blocker
|
||||
else
|
||||
blocker
|
||||
end
|
||||
|
||||
if following?(blocked, blocker) do
|
||||
unfollow(blocked, blocker)
|
||||
end
|
||||
|
||||
{:ok, blocker} = update_follower_count(blocker)
|
||||
|
||||
info_cng =
|
||||
blocker.info
|
||||
|> User.Info.add_to_block(ap_id)
|
||||
|
|
@ -1021,12 +1048,21 @@ defmodule Pleroma.User do
|
|||
end)
|
||||
end
|
||||
|
||||
def subscribed_to?(user, %{ap_id: ap_id}) do
|
||||
with %User{} = target <- User.get_by_ap_id(ap_id) do
|
||||
Enum.member?(target.info.subscribers, user.ap_id)
|
||||
end
|
||||
end
|
||||
|
||||
def muted_users(user),
|
||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
|
||||
|
||||
def blocked_users(user),
|
||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
|
||||
|
||||
def subscribers(user),
|
||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers))
|
||||
|
||||
def block_domain(user, domain) do
|
||||
info_cng =
|
||||
user.info
|
||||
|
|
@ -1063,6 +1099,42 @@ defmodule Pleroma.User do
|
|||
)
|
||||
end
|
||||
|
||||
def maybe_external_user_query(query, external) do
|
||||
if external, do: external_user_query(query), else: query
|
||||
end
|
||||
|
||||
def external_user_query(query \\ User) do
|
||||
from(
|
||||
u in query,
|
||||
where: u.local == false,
|
||||
where: not is_nil(u.nickname)
|
||||
)
|
||||
end
|
||||
|
||||
def maybe_active_user_query(query, active) do
|
||||
if active, do: active_user_query(query), else: query
|
||||
end
|
||||
|
||||
def active_user_query(query \\ User) do
|
||||
from(
|
||||
u in query,
|
||||
where: fragment("not (?->'deactivated' @> 'true')", u.info),
|
||||
where: not is_nil(u.nickname)
|
||||
)
|
||||
end
|
||||
|
||||
def maybe_deactivated_user_query(query, deactivated) do
|
||||
if deactivated, do: deactivated_user_query(query), else: query
|
||||
end
|
||||
|
||||
def deactivated_user_query(query \\ User) do
|
||||
from(
|
||||
u in query,
|
||||
where: fragment("(?->'deactivated' @> 'true')", u.info),
|
||||
where: not is_nil(u.nickname)
|
||||
)
|
||||
end
|
||||
|
||||
def active_local_user_query do
|
||||
from(
|
||||
u in local_user_query(),
|
||||
|
|
@ -1087,39 +1159,48 @@ defmodule Pleroma.User do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def update_notification_settings(%User{} = user, settings \\ %{}) do
|
||||
info_changeset = User.Info.update_notification_settings(user.info, settings)
|
||||
|
||||
change(user)
|
||||
|> put_embed(:info, info_changeset)
|
||||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def delete(%User{} = user) do
|
||||
{:ok, user} = User.deactivate(user)
|
||||
|
||||
# Remove all relationships
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
followers
|
||||
|> Enum.each(fn follower -> User.unfollow(follower, user) end)
|
||||
Enum.each(followers, fn follower -> User.unfollow(follower, user) end)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
friends
|
||||
|> Enum.each(fn followed -> User.unfollow(user, followed) end)
|
||||
Enum.each(friends, fn followed -> User.unfollow(user, followed) end)
|
||||
|
||||
query = from(a in Activity, where: a.actor == ^user.ap_id)
|
||||
delete_user_activities(user)
|
||||
end
|
||||
|
||||
Repo.all(query)
|
||||
|> Enum.each(fn activity ->
|
||||
case activity.data["type"] do
|
||||
"Create" ->
|
||||
ActivityPub.delete(Object.normalize(activity.data["object"]))
|
||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||
Activity
|
||||
|> where(actor: ^ap_id)
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Repo.all()
|
||||
|> Enum.each(fn
|
||||
%{data: %{"type" => "Create"}} = activity ->
|
||||
activity |> Object.normalize() |> ActivityPub.delete()
|
||||
|
||||
# TODO: Do something with likes, follows, repeats.
|
||||
_ ->
|
||||
"Doing nothing"
|
||||
end
|
||||
# TODO: Do something with likes, follows, repeats.
|
||||
_ ->
|
||||
"Doing nothing"
|
||||
end)
|
||||
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
def disable_async(user, status \\ true) do
|
||||
Pleroma.Jobs.enqueue(:user, __MODULE__, [:disable_async, user, status])
|
||||
PleromaJobQueue.enqueue(:user, __MODULE__, [:disable_async, user, status])
|
||||
end
|
||||
|
||||
def disable(%User{} = user, status \\ true) do
|
||||
|
|
@ -1146,24 +1227,39 @@ defmodule Pleroma.User do
|
|||
|
||||
def html_filter_policy(_), do: @default_scrubbers
|
||||
|
||||
def fetch_by_ap_id(ap_id) do
|
||||
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
||||
case ap_try do
|
||||
{:ok, user} ->
|
||||
user
|
||||
|
||||
_ ->
|
||||
case OStatus.make_user(ap_id) do
|
||||
{:ok, user} -> user
|
||||
_ -> {:error, "Could not fetch by AP id"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_or_fetch_by_ap_id(ap_id) do
|
||||
user = get_by_ap_id(ap_id)
|
||||
|
||||
if !is_nil(user) and !User.needs_update?(user) do
|
||||
user
|
||||
else
|
||||
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||
# Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled)
|
||||
should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
|
||||
|
||||
case ap_try do
|
||||
{:ok, user} ->
|
||||
user
|
||||
user = fetch_by_ap_id(ap_id)
|
||||
|
||||
_ ->
|
||||
case OStatus.make_user(ap_id) do
|
||||
{:ok, user} -> user
|
||||
_ -> {:error, "Could not fetch by AP id"}
|
||||
end
|
||||
if should_fetch_initial do
|
||||
with %User{} = user do
|
||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1239,8 +1335,8 @@ defmodule Pleroma.User do
|
|||
# this is because we have synchronous follow APIs and need to simulate them
|
||||
# with an async handshake
|
||||
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
|
||||
with %User{} = a <- Repo.get(User, a.id),
|
||||
%User{} = b <- Repo.get(User, b.id) do
|
||||
with %User{} = a <- User.get_by_id(a.id),
|
||||
%User{} = b <- User.get_by_id(b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
|
|
@ -1250,8 +1346,8 @@ defmodule Pleroma.User do
|
|||
|
||||
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
|
||||
with :ok <- :timer.sleep(timeout),
|
||||
%User{} = a <- Repo.get(User, a.id),
|
||||
%User{} = b <- Repo.get(User, b.id) do
|
||||
%User{} = a <- User.get_by_id(a.id),
|
||||
%User{} = b <- User.get_by_id(b.id) do
|
||||
{:ok, a, b}
|
||||
else
|
||||
_e ->
|
||||
|
|
@ -1338,7 +1434,7 @@ defmodule Pleroma.User do
|
|||
|> Enum.map(&String.downcase(&1))
|
||||
end
|
||||
|
||||
defp local_nickname_regex() do
|
||||
defp local_nickname_regex do
|
||||
if Pleroma.Config.get([:instance, :extended_nickname_format]) do
|
||||
@extended_local_nickname_regex
|
||||
else
|
||||
|
|
@ -1381,4 +1477,8 @@ defmodule Pleroma.User do
|
|||
offset: ^((page - 1) * page_size)
|
||||
)
|
||||
end
|
||||
|
||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||
target.ap_id not in user.info.muted_reblogs
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.User.Info do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.User.Info
|
||||
|
||||
embedded_schema do
|
||||
field(:banner, :map, default: %{})
|
||||
field(:background, :map, default: %{})
|
||||
|
|
@ -19,6 +21,8 @@ defmodule Pleroma.User.Info do
|
|||
field(:blocks, {:array, :string}, default: [])
|
||||
field(:domain_blocks, {:array, :string}, default: [])
|
||||
field(:mutes, {:array, :string}, default: [])
|
||||
field(:muted_reblogs, {:array, :string}, default: [])
|
||||
field(:subscribers, {:array, :string}, default: [])
|
||||
field(:deactivated, :boolean, default: false)
|
||||
field(:no_rich_text, :boolean, default: false)
|
||||
field(:ap_enabled, :boolean, default: false)
|
||||
|
|
@ -38,6 +42,10 @@ defmodule Pleroma.User.Info do
|
|||
field(:flavour, :string, default: nil)
|
||||
field(:disabled, :boolean, default: false)
|
||||
|
||||
field(:notification_settings, :map,
|
||||
default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
|
||||
)
|
||||
|
||||
# Found in the wild
|
||||
# ap_id -> Where is this used?
|
||||
# bio -> Where is this used?
|
||||
|
|
@ -55,6 +63,19 @@ defmodule Pleroma.User.Info do
|
|||
|> validate_required([:deactivated])
|
||||
end
|
||||
|
||||
def update_notification_settings(info, settings) do
|
||||
notification_settings =
|
||||
info.notification_settings
|
||||
|> Map.merge(settings)
|
||||
|> Map.take(["remote", "local", "followers", "follows"])
|
||||
|
||||
params = %{notification_settings: notification_settings}
|
||||
|
||||
info
|
||||
|> cast(params, [:notification_settings])
|
||||
|> validate_required([:notification_settings])
|
||||
end
|
||||
|
||||
def set_disabled_status(info, disabled) do
|
||||
params = %{disabled: disabled}
|
||||
|
||||
|
|
@ -99,6 +120,14 @@ defmodule Pleroma.User.Info do
|
|||
|> validate_required([:blocks])
|
||||
end
|
||||
|
||||
def set_subscribers(info, subscribers) do
|
||||
params = %{subscribers: subscribers}
|
||||
|
||||
info
|
||||
|> cast(params, [:subscribers])
|
||||
|> validate_required([:subscribers])
|
||||
end
|
||||
|
||||
def add_to_mutes(info, muted) do
|
||||
set_mutes(info, Enum.uniq([muted | info.mutes]))
|
||||
end
|
||||
|
|
@ -115,6 +144,14 @@ defmodule Pleroma.User.Info do
|
|||
set_blocks(info, List.delete(info.blocks, blocked))
|
||||
end
|
||||
|
||||
def add_to_subscribers(info, subscribed) do
|
||||
set_subscribers(info, Enum.uniq([subscribed | info.subscribers]))
|
||||
end
|
||||
|
||||
def remove_from_subscribers(info, subscribed) do
|
||||
set_subscribers(info, List.delete(info.subscribers, subscribed))
|
||||
end
|
||||
|
||||
def set_domain_blocks(info, domain_blocks) do
|
||||
params = %{domain_blocks: domain_blocks}
|
||||
|
||||
|
|
@ -259,4 +296,23 @@ defmodule Pleroma.User.Info do
|
|||
|
||||
cast(info, params, [:pinned_activities])
|
||||
end
|
||||
|
||||
def roles(%Info{is_moderator: is_moderator, is_admin: is_admin}) do
|
||||
%{
|
||||
admin: is_admin,
|
||||
moderator: is_moderator
|
||||
}
|
||||
end
|
||||
|
||||
def add_reblog_mute(info, ap_id) do
|
||||
params = %{muted_reblogs: info.muted_reblogs ++ [ap_id]}
|
||||
|
||||
cast(info, params, [:muted_reblogs])
|
||||
end
|
||||
|
||||
def remove_reblog_mute(info, ap_id) do
|
||||
params = %{muted_reblogs: List.delete(info.muted_reblogs, ap_id)}
|
||||
|
||||
cast(info, params, [:muted_reblogs])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ defmodule Pleroma.User.WelcomeMessage do
|
|||
end
|
||||
end
|
||||
|
||||
defp welcome_user() do
|
||||
defp welcome_user do
|
||||
with nickname when is_binary(nickname) <-
|
||||
Pleroma.Config.get([:instance, :welcome_user_nickname]),
|
||||
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.User.WelcomeMessage do
|
|||
end
|
||||
end
|
||||
|
||||
defp welcome_message() do
|
||||
defp welcome_message do
|
||||
Pleroma.Config.get([:instance, :welcome_message])
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,40 +6,119 @@ defmodule Pleroma.UserInviteToken do
|
|||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.UserInviteToken
|
||||
import Ecto.Query
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.UserInviteToken
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
@type token :: String.t()
|
||||
|
||||
schema "user_invite_tokens" do
|
||||
field(:token, :string)
|
||||
field(:used, :boolean, default: false)
|
||||
field(:max_use, :integer)
|
||||
field(:expires_at, :date)
|
||||
field(:uses, :integer, default: 0)
|
||||
field(:invite_type, :string)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def create_token do
|
||||
@spec create_invite(map()) :: UserInviteToken.t()
|
||||
def create_invite(params \\ %{}) do
|
||||
%UserInviteToken{}
|
||||
|> cast(params, [:max_use, :expires_at])
|
||||
|> add_token()
|
||||
|> assign_type()
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
defp add_token(changeset) do
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
|
||||
token = %UserInviteToken{
|
||||
used: false,
|
||||
token: token
|
||||
}
|
||||
|
||||
Repo.insert(token)
|
||||
put_change(changeset, :token, token)
|
||||
end
|
||||
|
||||
def used_changeset(struct) do
|
||||
struct
|
||||
|> cast(%{}, [])
|
||||
|> put_change(:used, true)
|
||||
defp assign_type(%{changes: %{max_use: _max_use, expires_at: _expires_at}} = changeset) do
|
||||
put_change(changeset, :invite_type, "reusable_date_limited")
|
||||
end
|
||||
|
||||
def mark_as_used(token) do
|
||||
with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
|
||||
{:ok, token} <- Repo.update(used_changeset(token)) do
|
||||
{:ok, token}
|
||||
else
|
||||
_e -> {:error, token}
|
||||
defp assign_type(%{changes: %{expires_at: _expires_at}} = changeset) do
|
||||
put_change(changeset, :invite_type, "date_limited")
|
||||
end
|
||||
|
||||
defp assign_type(%{changes: %{max_use: _max_use}} = changeset) do
|
||||
put_change(changeset, :invite_type, "reusable")
|
||||
end
|
||||
|
||||
defp assign_type(changeset), do: put_change(changeset, :invite_type, "one_time")
|
||||
|
||||
@spec list_invites() :: [UserInviteToken.t()]
|
||||
def list_invites do
|
||||
query = from(u in UserInviteToken, order_by: u.id)
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@spec update_invite!(UserInviteToken.t(), map()) :: UserInviteToken.t() | no_return()
|
||||
def update_invite!(invite, changes) do
|
||||
change(invite, changes) |> Repo.update!()
|
||||
end
|
||||
|
||||
@spec update_invite(UserInviteToken.t(), map()) ::
|
||||
{:ok, UserInviteToken.t()} | {:error, Changeset.t()}
|
||||
def update_invite(invite, changes) do
|
||||
change(invite, changes) |> Repo.update()
|
||||
end
|
||||
|
||||
@spec find_by_token!(token()) :: UserInviteToken.t() | no_return()
|
||||
def find_by_token!(token), do: Repo.get_by!(UserInviteToken, token: token)
|
||||
|
||||
@spec find_by_token(token()) :: {:ok, UserInviteToken.t()} | nil
|
||||
def find_by_token(token) do
|
||||
with invite <- Repo.get_by(UserInviteToken, token: token) do
|
||||
{:ok, invite}
|
||||
end
|
||||
end
|
||||
|
||||
@spec valid_invite?(UserInviteToken.t()) :: boolean()
|
||||
def valid_invite?(%{invite_type: "one_time"} = invite) do
|
||||
not invite.used
|
||||
end
|
||||
|
||||
def valid_invite?(%{invite_type: "date_limited"} = invite) do
|
||||
not_overdue_date?(invite) and not invite.used
|
||||
end
|
||||
|
||||
def valid_invite?(%{invite_type: "reusable"} = invite) do
|
||||
invite.uses < invite.max_use and not invite.used
|
||||
end
|
||||
|
||||
def valid_invite?(%{invite_type: "reusable_date_limited"} = invite) do
|
||||
not_overdue_date?(invite) and invite.uses < invite.max_use and not invite.used
|
||||
end
|
||||
|
||||
defp not_overdue_date?(%{expires_at: expires_at}) do
|
||||
Date.compare(Date.utc_today(), expires_at) in [:lt, :eq]
|
||||
end
|
||||
|
||||
@spec update_usage!(UserInviteToken.t()) :: nil | UserInviteToken.t() | no_return()
|
||||
def update_usage!(%{invite_type: "date_limited"}), do: nil
|
||||
|
||||
def update_usage!(%{invite_type: "one_time"} = invite),
|
||||
do: update_invite!(invite, %{used: true})
|
||||
|
||||
def update_usage!(%{invite_type: invite_type} = invite)
|
||||
when invite_type == "reusable" or invite_type == "reusable_date_limited" do
|
||||
changes = %{
|
||||
uses: invite.uses + 1
|
||||
}
|
||||
|
||||
changes =
|
||||
if changes.uses >= invite.max_use do
|
||||
Map.put(changes, :used, true)
|
||||
else
|
||||
changes
|
||||
end
|
||||
|
||||
update_invite!(invite, changes)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Upload
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
import Ecto.Query
|
||||
import Pleroma.Web.ActivityPub.Utils
|
||||
|
|
@ -89,15 +89,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor}
|
||||
end
|
||||
|
||||
def insert(map, local \\ true) when is_map(map) do
|
||||
def increase_replies_count_if_reply(%{
|
||||
"object" =>
|
||||
%{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object,
|
||||
"type" => "Create"
|
||||
}) do
|
||||
if is_public?(object) do
|
||||
Activity.increase_replies_count(reply_status_id)
|
||||
Object.increase_replies_count(reply_ap_id)
|
||||
end
|
||||
end
|
||||
|
||||
def increase_replies_count_if_reply(_create_data), do: :noop
|
||||
|
||||
def decrease_replies_count_if_reply(%Object{
|
||||
data: %{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object
|
||||
}) do
|
||||
if is_public?(object) do
|
||||
Activity.decrease_replies_count(reply_status_id)
|
||||
Object.decrease_replies_count(reply_ap_id)
|
||||
end
|
||||
end
|
||||
|
||||
def decrease_replies_count_if_reply(_object), do: :noop
|
||||
|
||||
def insert(map, local \\ true, fake \\ false) when is_map(map) do
|
||||
with nil <- Activity.normalize(map),
|
||||
map <- lazy_put_activity_defaults(map),
|
||||
map <- lazy_put_activity_defaults(map, fake),
|
||||
:ok <- check_actor_is_active(map["actor"]),
|
||||
{_, true} <- {:remote_limit_error, check_remote_limit(map)},
|
||||
{:ok, map} <- MRF.filter(map),
|
||||
:ok <- insert_full_object(map) do
|
||||
{recipients, _, _} = get_recipients(map)
|
||||
|
||||
{recipients, _, _} = get_recipients(map),
|
||||
{:fake, false, map, recipients} <- {:fake, fake, map, recipients},
|
||||
{:ok, object} <- insert_full_object(map) do
|
||||
{:ok, activity} =
|
||||
Repo.insert(%Activity{
|
||||
data: map,
|
||||
|
|
@ -106,6 +130,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
recipients: recipients
|
||||
})
|
||||
|
||||
# Splice in the child object if we have one.
|
||||
activity =
|
||||
if !is_nil(object) do
|
||||
Map.put(activity, :object, object)
|
||||
else
|
||||
activity
|
||||
end
|
||||
|
||||
Task.start(fn ->
|
||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
end)
|
||||
|
|
@ -114,8 +146,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
stream_out(activity)
|
||||
{:ok, activity}
|
||||
else
|
||||
%Activity{} = activity -> {:ok, activity}
|
||||
error -> {:error, error}
|
||||
%Activity{} = activity ->
|
||||
{:ok, activity}
|
||||
|
||||
{:fake, true, map, recipients} ->
|
||||
activity = %Activity{
|
||||
data: map,
|
||||
local: local,
|
||||
actor: map["actor"],
|
||||
recipients: recipients,
|
||||
id: "pleroma:fakeid"
|
||||
}
|
||||
|
||||
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
|
||||
{:ok, activity}
|
||||
|
||||
error ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -158,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def create(%{to: to, actor: actor, context: context, object: object} = params) do
|
||||
def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do
|
||||
additional = params[:additional] || %{}
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
|
|
@ -169,11 +216,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
%{to: to, actor: actor, published: published, context: context, object: object},
|
||||
additional
|
||||
),
|
||||
{:ok, activity} <- insert(create_data, local),
|
||||
# Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
|
||||
{:ok, activity} <- insert(create_data, local, fake),
|
||||
{:fake, false, activity} <- {:fake, fake, activity},
|
||||
_ <- increase_replies_count_if_reply(create_data),
|
||||
# Changing note count prior to enqueuing federation task in order to avoid
|
||||
# race conditions on updating user.info
|
||||
{:ok, _actor} <- increase_note_count_if_public(actor, activity),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:fake, true, activity} ->
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -309,17 +362,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
||||
|
||||
data = %{
|
||||
"type" => "Delete",
|
||||
"actor" => actor,
|
||||
"object" => id,
|
||||
"to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
|
||||
with {:ok, _} <- Object.delete(object),
|
||||
with {:ok, object, activity} <- Object.delete(object),
|
||||
data <- %{
|
||||
"type" => "Delete",
|
||||
"actor" => actor,
|
||||
"object" => id,
|
||||
"to" => to,
|
||||
"deleted_activity_id" => activity && activity.id
|
||||
},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
# Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info
|
||||
_ <- decrease_replies_count_if_reply(object),
|
||||
# Changing note count prior to enqueuing federation task in order to avoid
|
||||
# race conditions on updating user.info
|
||||
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
||||
:ok <- maybe_federate(activity) do
|
||||
{:ok, activity}
|
||||
|
|
@ -367,20 +423,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
content: content
|
||||
} = params
|
||||
) do
|
||||
additional = params[:additional] || %{}
|
||||
|
||||
# only accept false as false value
|
||||
local = !(params[:local] == false)
|
||||
forward = !(params[:forward] == false)
|
||||
|
||||
%{
|
||||
additional = params[:additional] || %{}
|
||||
|
||||
params = %{
|
||||
actor: actor,
|
||||
context: context,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content
|
||||
}
|
||||
|> make_flag_data(additional)
|
||||
|> insert(local)
|
||||
|
||||
additional =
|
||||
if forward do
|
||||
Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]})
|
||||
else
|
||||
Map.merge(additional, %{"to" => [], "cc" => []})
|
||||
end
|
||||
|
||||
with flag_data <- make_flag_data(params, additional),
|
||||
{:ok, activity} <- insert(flag_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
Enum.each(User.all_superusers(), fn superuser ->
|
||||
superuser
|
||||
|> Pleroma.AdminEmail.report(actor, account, statuses, content)
|
||||
|> Pleroma.Mailer.deliver_async()
|
||||
end)
|
||||
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
|
|
@ -409,6 +483,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
),
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
|> Activity.with_preloaded_object()
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
|
@ -501,7 +576,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
when is_list(tag_reject) and tag_reject != [] do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject)
|
||||
where: fragment(~s(\(not \(? #> '{"object","tag"}'\) \\?| ?\)), activity.data, ^tag_reject)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -511,7 +586,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
when is_list(tag_all) and tag_all != [] do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all)
|
||||
where: fragment(~s(\(? #> '{"object","tag"}'\) \\?& ?), activity.data, ^tag_all)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -520,14 +595,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag)
|
||||
where: fragment(~s(\(? #> '{"object","tag"}'\) \\?| ?), activity.data, ^tag)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
|
||||
where: fragment(~s(? <@ (? #> '{"object","tag"}'\)), ^tag, activity.data)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -600,7 +675,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
|
||||
where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -609,7 +684,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
|
||||
where: fragment(~s(not (? #> '{"object","attachment"}' = ?\)), activity.data, ^[])
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -676,6 +751,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
defp restrict_pinned(query, _), do: query
|
||||
|
||||
defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do
|
||||
muted_reblogs = info.muted_reblogs || []
|
||||
|
||||
from(
|
||||
activity in query,
|
||||
where:
|
||||
fragment(
|
||||
"not ( ?->>'type' = 'Announce' and ? = ANY(?))",
|
||||
activity.data,
|
||||
activity.actor,
|
||||
^muted_reblogs
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
defp restrict_muted_reblogs(query, _), do: query
|
||||
|
||||
defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query
|
||||
|
||||
defp maybe_preload_objects(query, _) do
|
||||
query
|
||||
|> Activity.with_preloaded_object()
|
||||
end
|
||||
|
||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||
base_query =
|
||||
from(
|
||||
|
|
@ -685,6 +784,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
)
|
||||
|
||||
base_query
|
||||
|> maybe_preload_objects(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|> restrict_tag(opts)
|
||||
|> restrict_tag_reject(opts)
|
||||
|
|
@ -703,6 +803,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> restrict_replies(opts)
|
||||
|> restrict_reblogs(opts)
|
||||
|> restrict_pinned(opts)
|
||||
|> restrict_muted_reblogs(opts)
|
||||
|> Activity.restrict_disabled_users()
|
||||
end
|
||||
|
||||
|
|
@ -907,7 +1008,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
},
|
||||
:ok <- Transmogrifier.contain_origin(id, params),
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, Object.normalize(activity.data["object"])}
|
||||
{:ok, Object.normalize(activity)}
|
||||
else
|
||||
{:error, {:reject, nil}} ->
|
||||
{:reject, nil}
|
||||
|
|
@ -919,7 +1020,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
|
||||
|
||||
case OStatus.fetch_activity_from_url(id) do
|
||||
{:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
|
||||
{:ok, [activity | _]} -> {:ok, Object.normalize(activity)}
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,15 +6,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.UserView
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
require Logger
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
|||
end)
|
||||
end
|
||||
|
||||
def get_policies() do
|
||||
def get_policies do
|
||||
Application.get_env(:pleroma, :instance, [])
|
||||
|> Keyword.get(:rewrite_policy, [])
|
||||
|> get_policies()
|
||||
|
|
|
|||
|
|
@ -23,15 +23,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
|
|||
defp score_displayname(_), do: 0.0
|
||||
|
||||
defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do
|
||||
# nickname will always be a binary string because it's generated by Pleroma.
|
||||
nick_score =
|
||||
nickname
|
||||
|> String.downcase()
|
||||
|> score_nickname()
|
||||
|
||||
# displayname will either be a binary string or nil, if a displayname isn't set.
|
||||
name_score =
|
||||
displayname
|
||||
|> String.downcase()
|
||||
|> score_displayname()
|
||||
if is_binary(displayname) do
|
||||
displayname
|
||||
|> String.downcase()
|
||||
|> score_displayname()
|
||||
else
|
||||
0.0
|
||||
end
|
||||
|
||||
nick_score + name_score
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
defp string_matches?(string, _) when not is_binary(string) do
|
||||
false
|
||||
end
|
||||
|
||||
defp string_matches?(string, pattern) when is_binary(pattern) do
|
||||
String.contains?(string, pattern)
|
||||
end
|
||||
|
|
@ -44,14 +48,29 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
|||
end
|
||||
|
||||
defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
|
||||
content =
|
||||
if is_binary(content) do
|
||||
content
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
summary =
|
||||
if is_binary(summary) do
|
||||
summary
|
||||
else
|
||||
""
|
||||
end
|
||||
|
||||
{content, summary} =
|
||||
Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern,
|
||||
replacement},
|
||||
{content_acc,
|
||||
summary_acc} ->
|
||||
{String.replace(content_acc, pattern, replacement),
|
||||
String.replace(summary_acc, pattern, replacement)}
|
||||
end)
|
||||
Enum.reduce(
|
||||
Pleroma.Config.get([:mrf_keyword, :replace]),
|
||||
{content, summary},
|
||||
fn {pattern, replacement}, {content_acc, summary_acc} ->
|
||||
{String.replace(content_acc, pattern, replacement),
|
||||
String.replace(summary_acc, pattern, replacement)}
|
||||
end
|
||||
)
|
||||
|
||||
{:ok,
|
||||
message
|
||||
|
|
@ -59,11 +78,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
|||
|> put_in(["object", "summary"], summary)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"object" => %{"content" => nil}} = message) do
|
||||
{:ok, message}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message) do
|
||||
with {:ok, message} <- check_reject(message),
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.Relay do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
require Logger
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
|
||||
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
|
||||
with %User{} = user <- get_actor(),
|
||||
%Object{} = object <- Object.normalize(activity.data["object"]["id"]) do
|
||||
%Object{} = object <- Object.normalize(activity) do
|
||||
ActivityPub.announce(user, object, nil, true, false)
|
||||
else
|
||||
e -> Logger.error("error: #{inspect(e)}")
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
A module to handle coding from internal to wire ActivityPub and back.
|
||||
"""
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
|
@ -83,14 +83,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> fix_content_map
|
||||
|> fix_likes
|
||||
|> fix_addressing
|
||||
|> fix_summary
|
||||
end
|
||||
|
||||
def fix_summary(%{"summary" => nil} = object) do
|
||||
object
|
||||
|> Map.put("summary", "")
|
||||
end
|
||||
|
||||
def fix_summary(%{"summary" => _} = object) do
|
||||
# summary is present, nothing to do
|
||||
object
|
||||
end
|
||||
|
||||
def fix_summary(object) do
|
||||
object
|
||||
|> Map.put("summary", "")
|
||||
end
|
||||
|
||||
def fix_addressing_list(map, field) do
|
||||
if is_binary(map[field]) do
|
||||
map
|
||||
|> Map.put(field, [map[field]])
|
||||
else
|
||||
map
|
||||
cond do
|
||||
is_binary(map[field]) ->
|
||||
Map.put(map, field, [map[field]])
|
||||
|
||||
is_nil(map[field]) ->
|
||||
Map.put(map, field, [])
|
||||
|
||||
true ->
|
||||
map
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -128,13 +148,42 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> fix_explicit_addressing(explicit_mentions)
|
||||
end
|
||||
|
||||
# if as:Public is addressed, then make sure the followers collection is also addressed
|
||||
# so that the activities will be delivered to local users.
|
||||
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
|
||||
recipients = to ++ cc
|
||||
|
||||
if followers_collection not in recipients do
|
||||
cond do
|
||||
"https://www.w3.org/ns/activitystreams#Public" in cc ->
|
||||
to = to ++ [followers_collection]
|
||||
Map.put(object, "to", to)
|
||||
|
||||
"https://www.w3.org/ns/activitystreams#Public" in to ->
|
||||
cc = cc ++ [followers_collection]
|
||||
Map.put(object, "cc", cc)
|
||||
|
||||
true ->
|
||||
object
|
||||
end
|
||||
else
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
def fix_implicit_addressing(object, _), do: object
|
||||
|
||||
def fix_addressing(object) do
|
||||
%User{} = user = User.get_or_fetch_by_ap_id(object["actor"])
|
||||
followers_collection = User.ap_followers(user)
|
||||
|
||||
object
|
||||
|> fix_addressing_list("to")
|
||||
|> fix_addressing_list("cc")
|
||||
|> fix_addressing_list("bto")
|
||||
|> fix_addressing_list("bcc")
|
||||
|> fix_explicit_addressing
|
||||
|> fix_implicit_addressing(followers_collection)
|
||||
end
|
||||
|
||||
def fix_actor(%{"attributedTo" => actor} = object) do
|
||||
|
|
@ -355,6 +404,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
|
||||
# with nil ID.
|
||||
def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do
|
||||
with context <- data["context"] || Utils.generate_context_id(),
|
||||
content <- data["content"] || "",
|
||||
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
||||
|
||||
# Reduce the object list to find the reported user.
|
||||
%User{} = account <-
|
||||
Enum.reduce_while(objects, nil, fn ap_id, _ ->
|
||||
with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
|
||||
{:halt, user}
|
||||
else
|
||||
_ -> {:cont, nil}
|
||||
end
|
||||
end),
|
||||
|
||||
# Remove the reported user from the object list.
|
||||
statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do
|
||||
params = %{
|
||||
actor: actor,
|
||||
context: context,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content,
|
||||
additional: %{
|
||||
"cc" => [account.ap_id]
|
||||
}
|
||||
}
|
||||
|
||||
ActivityPub.flag(params)
|
||||
end
|
||||
end
|
||||
|
||||
# disallow objects with bogus IDs
|
||||
def handle_incoming(%{"id" => nil}), do: :error
|
||||
def handle_incoming(%{"id" => ""}), do: :error
|
||||
|
|
@ -650,10 +733,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
if object = Object.normalize(id), do: {:ok, object}, else: nil
|
||||
end
|
||||
|
||||
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) when is_binary(inReplyTo) do
|
||||
with false <- String.starts_with?(inReplyTo, "http"),
|
||||
{:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do
|
||||
Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo)
|
||||
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
|
||||
with false <- String.starts_with?(in_reply_to, "http"),
|
||||
{:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do
|
||||
Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to)
|
||||
else
|
||||
_e -> object
|
||||
end
|
||||
|
|
@ -736,6 +819,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def prepare_outgoing(%{"type" => _type} = data) do
|
||||
data =
|
||||
data
|
||||
|> strip_internal_fields
|
||||
|> maybe_fix_object_url
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
|
||||
|
|
@ -829,10 +913,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
|
||||
def add_attributed_to(object) do
|
||||
attributedTo = object["attributedTo"] || object["actor"]
|
||||
attributed_to = object["attributedTo"] || object["actor"]
|
||||
|
||||
object
|
||||
|> Map.put("attributedTo", attributedTo)
|
||||
|> Map.put("attributedTo", attributed_to)
|
||||
end
|
||||
|
||||
def add_likes(%{"id" => id, "like_count" => likes} = object) do
|
||||
|
|
@ -870,7 +954,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
"announcements",
|
||||
"announcement_count",
|
||||
"emoji",
|
||||
"context_id"
|
||||
"context_id",
|
||||
"deleted_activity_id"
|
||||
])
|
||||
end
|
||||
|
||||
|
|
@ -885,8 +970,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|
||||
defp strip_internal_tags(object), do: object
|
||||
|
||||
defp user_upgrade_task(user) do
|
||||
old_follower_address = User.ap_followers(user)
|
||||
def perform(:user_upgrade, user) do
|
||||
# we pass a fake user so that the followers collection is stripped away
|
||||
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
|
||||
|
||||
q =
|
||||
from(
|
||||
|
|
@ -929,28 +1015,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
Repo.update_all(q, [])
|
||||
end
|
||||
|
||||
def upgrade_user_from_ap_id(ap_id, async \\ true) do
|
||||
def upgrade_user_from_ap_id(ap_id) do
|
||||
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
already_ap = User.ap_enabled?(user)
|
||||
|
||||
{:ok, user} =
|
||||
User.upgrade_changeset(user, data)
|
||||
|> Repo.update()
|
||||
|
||||
if !already_ap do
|
||||
# This could potentially take a long time, do it in the background
|
||||
if async do
|
||||
Task.start(fn ->
|
||||
user_upgrade_task(user)
|
||||
end)
|
||||
else
|
||||
user_upgrade_task(user)
|
||||
end
|
||||
{:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
|
||||
already_ap <- User.ap_enabled?(user),
|
||||
{:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
|
||||
unless already_ap do
|
||||
PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
|
||||
end
|
||||
|
||||
{:ok, user}
|
||||
else
|
||||
%User{} = user -> {:ok, user}
|
||||
e -> e
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,16 +3,17 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Ecto.Changeset
|
||||
alias Ecto.UUID
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
|
|
@ -98,7 +99,10 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
%{
|
||||
"@context" => [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld"
|
||||
"#{Web.base_url()}/schemas/litepub-0.1.jsonld",
|
||||
%{
|
||||
"@language" => "und"
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
|
@ -174,18 +178,26 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Adds an id and a published data if they aren't there,
|
||||
also adds it to an included object
|
||||
"""
|
||||
def lazy_put_activity_defaults(map) do
|
||||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"])
|
||||
|
||||
def lazy_put_activity_defaults(map, fake \\ false) do
|
||||
map =
|
||||
map
|
||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
|> Map.put_new("context", context)
|
||||
|> Map.put_new("context_id", context_id)
|
||||
unless fake do
|
||||
%{data: %{"id" => context}, id: context_id} = create_context(map["context"])
|
||||
|
||||
map
|
||||
|> Map.put_new_lazy("id", &generate_activity_id/0)
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
|> Map.put_new("context", context)
|
||||
|> Map.put_new("context_id", context_id)
|
||||
else
|
||||
map
|
||||
|> Map.put_new("id", "pleroma:fakeid")
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
|> Map.put_new("context", "pleroma:fakecontext")
|
||||
|> Map.put_new("context_id", -1)
|
||||
end
|
||||
|
||||
if is_map(map["object"]) do
|
||||
object = lazy_put_object_defaults(map["object"], map)
|
||||
object = lazy_put_object_defaults(map["object"], map, fake)
|
||||
%{map | "object" => object}
|
||||
else
|
||||
map
|
||||
|
|
@ -195,7 +207,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
@doc """
|
||||
Adds an id and published date if they aren't there.
|
||||
"""
|
||||
def lazy_put_object_defaults(map, activity \\ %{}) do
|
||||
def lazy_put_object_defaults(map, activity \\ %{}, fake)
|
||||
|
||||
def lazy_put_object_defaults(map, activity, true = _fake) do
|
||||
map
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
|> Map.put_new("id", "pleroma:fake_object_id")
|
||||
|> Map.put_new("context", activity["context"])
|
||||
|> Map.put_new("fake", true)
|
||||
|> Map.put_new("context_id", activity["context_id"])
|
||||
end
|
||||
|
||||
def lazy_put_object_defaults(map, activity, _fake) do
|
||||
map
|
||||
|> Map.put_new_lazy("id", &generate_object_id/0)
|
||||
|> Map.put_new_lazy("published", &make_date/0)
|
||||
|
|
@ -208,12 +231,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"""
|
||||
def insert_full_object(%{"object" => %{"type" => type} = object_data})
|
||||
when is_map(object_data) and type in @supported_object_types do
|
||||
with {:ok, _} <- Object.create(object_data) do
|
||||
:ok
|
||||
with {:ok, object} <- Object.create(object_data) do
|
||||
{:ok, object}
|
||||
end
|
||||
end
|
||||
|
||||
def insert_full_object(_), do: :ok
|
||||
def insert_full_object(_), do: {:ok, nil}
|
||||
|
||||
def update_object_in_activities(%{data: %{"id" => id}} = object) do
|
||||
# TODO
|
||||
|
|
@ -274,13 +297,31 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def make_like_data(%User{ap_id: ap_id} = actor, %{data: %{"id" => id}} = object, activity_id) do
|
||||
def make_like_data(
|
||||
%User{ap_id: ap_id} = actor,
|
||||
%{data: %{"actor" => object_actor_id, "id" => id}} = object,
|
||||
activity_id
|
||||
) do
|
||||
object_actor = User.get_cached_by_ap_id(object_actor_id)
|
||||
|
||||
to =
|
||||
if Visibility.is_public?(object) do
|
||||
[actor.follower_address, object.data["actor"]]
|
||||
else
|
||||
[object.data["actor"]]
|
||||
end
|
||||
|
||||
cc =
|
||||
(object.data["to"] ++ (object.data["cc"] || []))
|
||||
|> List.delete(actor.ap_id)
|
||||
|> List.delete(object_actor.follower_address)
|
||||
|
||||
data = %{
|
||||
"type" => "Like",
|
||||
"actor" => ap_id,
|
||||
"object" => id,
|
||||
"to" => [actor.follower_address, object.data["actor"]],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|
||||
|
|
@ -335,7 +376,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
[state, actor, object]
|
||||
)
|
||||
|
||||
activity = Repo.get(Activity, activity.id)
|
||||
activity = Activity.get_by_id(activity.id)
|
||||
{:ok, activity}
|
||||
rescue
|
||||
e ->
|
||||
|
|
@ -385,13 +426,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
activity.data
|
||||
),
|
||||
where: activity.actor == ^follower_id,
|
||||
# this is to use the index
|
||||
where:
|
||||
fragment(
|
||||
"? @> ?",
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
activity.data,
|
||||
^%{object: followed_id}
|
||||
activity.data,
|
||||
^followed_id
|
||||
),
|
||||
order_by: [desc: :id],
|
||||
order_by: [fragment("? desc nulls last", activity.id)],
|
||||
limit: 1
|
||||
)
|
||||
|
||||
|
|
@ -548,13 +591,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
activity.data
|
||||
),
|
||||
where: activity.actor == ^blocker_id,
|
||||
# this is to use the index
|
||||
where:
|
||||
fragment(
|
||||
"? @> ?",
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
activity.data,
|
||||
^%{object: blocked_id}
|
||||
activity.data,
|
||||
^blocked_id
|
||||
),
|
||||
order_by: [desc: :id],
|
||||
order_by: [fragment("? desc nulls last", activity.id)],
|
||||
limit: 1
|
||||
)
|
||||
|
||||
|
|
@ -602,7 +647,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
#### Flag-related helpers
|
||||
|
||||
def make_flag_data(params, additional) do
|
||||
status_ap_ids = Enum.map(params.statuses || [], & &1.data["id"])
|
||||
status_ap_ids =
|
||||
Enum.map(params.statuses || [], fn
|
||||
%Activity{} = act -> act.data["id"]
|
||||
act when is_map(act) -> act["id"]
|
||||
act when is_binary(act) -> act
|
||||
end)
|
||||
|
||||
object = [params.account.ap_id] ++ status_ap_ids
|
||||
|
||||
%{
|
||||
|
|
@ -614,4 +665,43 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
}
|
||||
|> Map.merge(additional)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after
|
||||
the first one to `pages_left` pages.
|
||||
If the amount of pages is higher than the collection has, it returns whatever was there.
|
||||
"""
|
||||
def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
||||
with {:ok, response} <- Tesla.get(from),
|
||||
{:ok, collection} <- Poison.decode(response.body) do
|
||||
case collection["type"] do
|
||||
"OrderedCollection" ->
|
||||
# If we've encountered the OrderedCollection and not the page,
|
||||
# just call the same function on the page address
|
||||
fetch_ordered_collection(collection["first"], pages_left)
|
||||
|
||||
"OrderedCollectionPage" ->
|
||||
if pages_left > 0 do
|
||||
# There are still more pages
|
||||
if Map.has_key?(collection, "next") do
|
||||
# There are still more pages, go deeper saving what we have into the accumulator
|
||||
fetch_ordered_collection(
|
||||
collection["next"],
|
||||
pages_left - 1,
|
||||
acc ++ collection["orderedItems"]
|
||||
)
|
||||
else
|
||||
# No more pages left, just return whatever we already have
|
||||
acc ++ collection["orderedItems"]
|
||||
end
|
||||
else
|
||||
# Got the amount of pages needed, add them all to the accumulator
|
||||
acc ++ collection["orderedItems"]
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, "Not an OrderedCollection or OrderedCollectionPage"}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
|||
|
||||
def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
object = Object.normalize(activity)
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|
|
@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
|||
|
||||
def render("object.json", %{object: %Activity{} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
object = Object.normalize(activity)
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|
|
|
|||
|
|
@ -5,15 +5,15 @@
|
|||
defmodule Pleroma.Web.ActivityPub.UserView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
|
|
@ -87,16 +87,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"publicKeyPem" => public_key
|
||||
},
|
||||
"endpoints" => endpoints,
|
||||
"icon" => %{
|
||||
"type" => "Image",
|
||||
"url" => User.avatar_url(user)
|
||||
},
|
||||
"image" => %{
|
||||
"type" => "Image",
|
||||
"url" => User.banner_url(user)
|
||||
},
|
||||
"tag" => user.info.source_data["tag"] || []
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|> Map.merge(Utils.make_json_ld_header())
|
||||
end
|
||||
|
||||
|
|
@ -294,4 +288,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
map
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_make_image(func, key, user) do
|
||||
if image = func.(user, no_default: true) do
|
||||
%{
|
||||
key => %{
|
||||
"type" => "Image",
|
||||
"url" => image
|
||||
}
|
||||
}
|
||||
else
|
||||
%{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,17 +3,19 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
@users_page_size 50
|
||||
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.MastodonAPI.Admin.AccountView
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
require Logger
|
||||
|
||||
@users_page_size 50
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
def user_delete(conn, %{"nickname" => nickname}) do
|
||||
|
|
@ -24,6 +26,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|> json(nickname)
|
||||
end
|
||||
|
||||
def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||
User.follow(follower, followed)
|
||||
end
|
||||
|
||||
conn
|
||||
|> json("ok")
|
||||
end
|
||||
|
||||
def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
|
||||
with %User{} = follower <- User.get_by_nickname(follower_nick),
|
||||
%User{} = followed <- User.get_by_nickname(followed_nick) do
|
||||
User.unfollow(follower, followed)
|
||||
end
|
||||
|
||||
conn
|
||||
|> json("ok")
|
||||
end
|
||||
|
||||
def user_create(
|
||||
conn,
|
||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||
|
|
@ -44,6 +66,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|> json(user.nickname)
|
||||
end
|
||||
|
||||
def user_show(conn, %{"nickname" => nickname}) do
|
||||
with %User{} = user <- User.get_by_nickname(nickname) do
|
||||
conn
|
||||
|> json(AccountView.render("show.json", %{user: user}))
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def user_toggle_disabled(conn, %{"nickname" => nickname}) do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
|
|
@ -75,8 +106,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|
||||
def list_users(conn, params) do
|
||||
{page, page_size} = page_params(params)
|
||||
filters = maybe_parse_filters(params["filters"])
|
||||
|
||||
with {:ok, users, count} <- User.all_for_admin(page, page_size),
|
||||
search_params = %{
|
||||
query: params["query"],
|
||||
page: page,
|
||||
page_size: page_size
|
||||
}
|
||||
|
||||
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
|
||||
do:
|
||||
conn
|
||||
|> json(
|
||||
|
|
@ -88,25 +126,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
)
|
||||
end
|
||||
|
||||
def search_users(%{assigns: %{user: admin}} = conn, %{"query" => query} = params) do
|
||||
{page, page_size} = page_params(params)
|
||||
@filters ~w(local external active deactivated)
|
||||
|
||||
with {:ok, users, count} <-
|
||||
User.search_for_admin(query, %{
|
||||
admin: admin,
|
||||
local: params["local"] == "true",
|
||||
page: page,
|
||||
page_size: page_size
|
||||
}),
|
||||
do:
|
||||
conn
|
||||
|> json(
|
||||
AccountView.render("index.json",
|
||||
users: users,
|
||||
count: count,
|
||||
page_size: page_size
|
||||
)
|
||||
)
|
||||
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
|
||||
|
||||
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
|
||||
defp maybe_parse_filters(filters) do
|
||||
filters
|
||||
|> String.split(",")
|
||||
|> Enum.filter(&Enum.member?(@filters, &1))
|
||||
|> Enum.map(&String.to_atom(&1))
|
||||
|> Enum.into(%{}, &{&1, true})
|
||||
end
|
||||
|
||||
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
|
||||
|
|
@ -216,7 +246,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
with true <-
|
||||
Pleroma.Config.get([:instance, :invites_enabled]) &&
|
||||
!Pleroma.Config.get([:instance, :registrations_open]),
|
||||
{:ok, invite_token} <- Pleroma.UserInviteToken.create_token(),
|
||||
{:ok, invite_token} <- UserInviteToken.create_invite(),
|
||||
email <-
|
||||
Pleroma.UserEmail.user_invitation_email(user, invite_token, email, params["name"]),
|
||||
{:ok, _} <- Pleroma.Mailer.deliver(email) do
|
||||
|
|
@ -225,11 +255,29 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end
|
||||
|
||||
@doc "Get a account registeration invite token (base64 string)"
|
||||
def get_invite_token(conn, _params) do
|
||||
{:ok, token} = Pleroma.UserInviteToken.create_token()
|
||||
def get_invite_token(conn, params) do
|
||||
options = params["invite"] || %{}
|
||||
{:ok, invite} = UserInviteToken.create_invite(options)
|
||||
|
||||
conn
|
||||
|> json(token.token)
|
||||
|> json(invite.token)
|
||||
end
|
||||
|
||||
@doc "Get list of created invites"
|
||||
def invites(conn, _params) do
|
||||
invites = UserInviteToken.list_invites()
|
||||
|
||||
conn
|
||||
|> json(AccountView.render("invites.json", %{invites: invites}))
|
||||
end
|
||||
|
||||
@doc "Revokes invite by token"
|
||||
def revoke_invite(conn, %{"token" => token}) do
|
||||
invite = UserInviteToken.find_by_token!(token)
|
||||
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true})
|
||||
|
||||
conn
|
||||
|> json(AccountView.render("invite.json", %{invite: updated_invite}))
|
||||
end
|
||||
|
||||
@doc "Get a password reset token (base64 string) for given nickname"
|
||||
|
|
@ -241,6 +289,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
|> json(token.token)
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
|
||||
def errors(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|
|
|
|||
54
lib/pleroma/web/admin_api/search.ex
Normal file
54
lib/pleroma/web/admin_api/search.ex
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.Search do
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@page_size 50
|
||||
|
||||
def user(%{query: term} = params) when is_nil(term) or term == "" do
|
||||
query = maybe_filtered_query(params)
|
||||
|
||||
paginated_query =
|
||||
maybe_filtered_query(params)
|
||||
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
|
||||
|
||||
count = query |> Repo.aggregate(:count, :id)
|
||||
|
||||
results = Repo.all(paginated_query)
|
||||
|
||||
{:ok, results, count}
|
||||
end
|
||||
|
||||
def user(%{query: term} = params) when is_binary(term) do
|
||||
search_query = from(u in maybe_filtered_query(params), where: ilike(u.nickname, ^"%#{term}%"))
|
||||
|
||||
count = search_query |> Repo.aggregate(:count, :id)
|
||||
|
||||
results =
|
||||
search_query
|
||||
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
|
||||
|> Repo.all()
|
||||
|
||||
{:ok, results, count}
|
||||
end
|
||||
|
||||
defp maybe_filtered_query(params) do
|
||||
from(u in User, order_by: u.nickname)
|
||||
|> User.maybe_local_user_query(params[:local])
|
||||
|> User.maybe_external_user_query(params[:external])
|
||||
|> User.maybe_active_user_query(params[:active])
|
||||
|> User.maybe_deactivated_user_query(params[:deactivated])
|
||||
end
|
||||
|
||||
defp paginate(query, page, page_size) do
|
||||
from(u in query,
|
||||
limit: ^page_size,
|
||||
offset: ^((page - 1) * page_size)
|
||||
)
|
||||
end
|
||||
end
|
||||
47
lib/pleroma/web/admin_api/views/account_view.ex
Normal file
47
lib/pleroma/web/admin_api/views/account_view.ex
Normal 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.Web.AdminAPI.AccountView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.User.Info
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
|
||||
def render("index.json", %{users: users, count: count, page_size: page_size}) do
|
||||
%{
|
||||
users: render_many(users, AccountView, "show.json", as: :user),
|
||||
count: count,
|
||||
page_size: page_size
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{user: user}) do
|
||||
%{
|
||||
"id" => user.id,
|
||||
"nickname" => user.nickname,
|
||||
"deactivated" => user.info.deactivated,
|
||||
"local" => user.local,
|
||||
"roles" => Info.roles(user.info),
|
||||
"tags" => user.tags || []
|
||||
}
|
||||
end
|
||||
|
||||
def render("invite.json", %{invite: invite}) do
|
||||
%{
|
||||
"id" => invite.id,
|
||||
"token" => invite.token,
|
||||
"used" => invite.used,
|
||||
"expires_at" => invite.expires_at,
|
||||
"uses" => invite.uses,
|
||||
"max_use" => invite.max_use,
|
||||
"invite_type" => invite.invite_type
|
||||
}
|
||||
end
|
||||
|
||||
def render("invites.json", %{invites: invites}) do
|
||||
%{
|
||||
invites: render_many(invites, AccountView, "invite.json", as: :invite)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.Authenticator do
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.User
|
||||
|
||||
def implementation do
|
||||
|
|
@ -12,14 +13,33 @@ defmodule Pleroma.Web.Auth.Authenticator do
|
|||
)
|
||||
end
|
||||
|
||||
@callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
|
||||
def get_user(plug), do: implementation().get_user(plug)
|
||||
@callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()}
|
||||
def get_user(plug, params), do: implementation().get_user(plug, params)
|
||||
|
||||
@callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) ::
|
||||
{:ok, User.t()} | {:error, any()}
|
||||
def create_from_registration(plug, params, registration),
|
||||
do: implementation().create_from_registration(plug, params, registration)
|
||||
|
||||
@callback get_registration(Plug.Conn.t(), Map.t()) ::
|
||||
{:ok, Registration.t()} | {:error, any()}
|
||||
def get_registration(plug, params),
|
||||
do: implementation().get_registration(plug, params)
|
||||
|
||||
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
||||
def handle_error(plug, error), do: implementation().handle_error(plug, error)
|
||||
|
||||
@callback auth_template() :: String.t() | nil
|
||||
def auth_template do
|
||||
implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
|
||||
# Note: `config :pleroma, :auth_template, "..."` support is deprecated
|
||||
implementation().auth_template() ||
|
||||
Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
|
||||
"show.html"
|
||||
end
|
||||
|
||||
@callback oauth_consumer_template() :: String.t() | nil
|
||||
def oauth_consumer_template do
|
||||
implementation().oauth_consumer_template() ||
|
||||
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
150
lib/pleroma/web/auth/ldap_authenticator.ex
Normal file
150
lib/pleroma/web/auth/ldap_authenticator.ex
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||
alias Pleroma.User
|
||||
|
||||
require Logger
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
@base Pleroma.Web.Auth.PleromaAuthenticator
|
||||
|
||||
@connection_timeout 10_000
|
||||
@search_timeout 10_000
|
||||
|
||||
defdelegate get_registration(conn, params), to: @base
|
||||
|
||||
defdelegate create_from_registration(conn, params, registration), to: @base
|
||||
|
||||
def get_user(%Plug.Conn{} = conn, params) do
|
||||
if Pleroma.Config.get([:ldap, :enabled]) do
|
||||
{name, password} =
|
||||
case params do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{name, password}
|
||||
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} ->
|
||||
{name, password}
|
||||
end
|
||||
|
||||
case ldap_user(name, password) do
|
||||
%User{} = user ->
|
||||
{:ok, user}
|
||||
|
||||
{:error, {:ldap_connection_error, _}} ->
|
||||
# When LDAP is unavailable, try default authenticator
|
||||
@base.get_user(conn, params)
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
else
|
||||
# Fall back to default authenticator
|
||||
@base.get_user(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_error(%Plug.Conn{} = _conn, error) do
|
||||
error
|
||||
end
|
||||
|
||||
def auth_template, do: nil
|
||||
|
||||
def oauth_consumer_template, do: nil
|
||||
|
||||
defp ldap_user(name, password) do
|
||||
ldap = Pleroma.Config.get(:ldap, [])
|
||||
host = Keyword.get(ldap, :host, "localhost")
|
||||
port = Keyword.get(ldap, :port, 389)
|
||||
ssl = Keyword.get(ldap, :ssl, false)
|
||||
sslopts = Keyword.get(ldap, :sslopts, [])
|
||||
|
||||
options =
|
||||
[{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
|
||||
if sslopts != [], do: [{:sslopts, sslopts}], else: []
|
||||
|
||||
case :eldap.open([to_charlist(host)], options) do
|
||||
{:ok, connection} ->
|
||||
try do
|
||||
if Keyword.get(ldap, :tls, false) do
|
||||
:application.ensure_all_started(:ssl)
|
||||
|
||||
case :eldap.start_tls(
|
||||
connection,
|
||||
Keyword.get(ldap, :tlsopts, []),
|
||||
@connection_timeout
|
||||
) do
|
||||
:ok ->
|
||||
:ok
|
||||
|
||||
error ->
|
||||
Logger.error("Could not start TLS: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
bind_user(connection, ldap, name, password)
|
||||
after
|
||||
:eldap.close(connection)
|
||||
end
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Could not open LDAP connection: #{inspect(error)}")
|
||||
{:error, {:ldap_connection_error, error}}
|
||||
end
|
||||
end
|
||||
|
||||
defp bind_user(connection, ldap, name, password) do
|
||||
uid = Keyword.get(ldap, :uid, "cn")
|
||||
base = Keyword.get(ldap, :base)
|
||||
|
||||
case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
|
||||
:ok ->
|
||||
case User.get_by_nickname_or_email(name) do
|
||||
%User{} = user ->
|
||||
user
|
||||
|
||||
_ ->
|
||||
register_user(connection, base, uid, name, password)
|
||||
end
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp register_user(connection, base, uid, name, password) do
|
||||
case :eldap.search(connection, [
|
||||
{:base, to_charlist(base)},
|
||||
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
||||
{:scope, :eldap.wholeSubtree()},
|
||||
{:attributes, ['mail', 'email']},
|
||||
{:timeout, @search_timeout}
|
||||
]) do
|
||||
{:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} ->
|
||||
with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do
|
||||
params = %{
|
||||
email: :erlang.list_to_binary(mail),
|
||||
name: name,
|
||||
nickname: name,
|
||||
password: password,
|
||||
password_confirmation: password
|
||||
}
|
||||
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
|
||||
case User.register(changeset) do
|
||||
{:ok, user} -> user
|
||||
error -> error
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}")
|
||||
{:error, :ldap_registration_missing_attributes}
|
||||
end
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,13 +3,22 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||
alias Pleroma.User
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
|
||||
def get_user(%Plug.Conn{} = conn) do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} = conn.params
|
||||
def get_user(%Plug.Conn{} = _conn, params) do
|
||||
{name, password} =
|
||||
case params do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{name, password}
|
||||
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} ->
|
||||
{name, password}
|
||||
end
|
||||
|
||||
with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)},
|
||||
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
|
||||
|
|
@ -20,9 +29,69 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
|||
end
|
||||
end
|
||||
|
||||
def get_registration(
|
||||
%Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
|
||||
_params
|
||||
) do
|
||||
registration = Registration.get_by_provider_uid(provider, uid)
|
||||
|
||||
if registration do
|
||||
{:ok, registration}
|
||||
else
|
||||
info = auth.info
|
||||
|
||||
Registration.changeset(%Registration{}, %{
|
||||
provider: to_string(provider),
|
||||
uid: to_string(uid),
|
||||
info: %{
|
||||
"nickname" => info.nickname,
|
||||
"email" => info.email,
|
||||
"name" => info.name,
|
||||
"description" => info.description
|
||||
}
|
||||
})
|
||||
|> Repo.insert()
|
||||
end
|
||||
end
|
||||
|
||||
def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
|
||||
|
||||
def create_from_registration(_conn, params, registration) do
|
||||
nickname = value([params["nickname"], Registration.nickname(registration)])
|
||||
email = value([params["email"], Registration.email(registration)])
|
||||
name = value([params["name"], Registration.name(registration)]) || nickname
|
||||
bio = value([params["bio"], Registration.description(registration)])
|
||||
|
||||
random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
|
||||
|
||||
with {:ok, new_user} <-
|
||||
User.register_changeset(
|
||||
%User{},
|
||||
%{
|
||||
email: email,
|
||||
nickname: nickname,
|
||||
name: name,
|
||||
bio: bio,
|
||||
password: random_password,
|
||||
password_confirmation: random_password
|
||||
},
|
||||
external: true,
|
||||
confirmed: true
|
||||
)
|
||||
|> Repo.insert(),
|
||||
{:ok, _} <-
|
||||
Registration.changeset(registration, %{user_id: new_user.id}) |> Repo.update() do
|
||||
{:ok, new_user}
|
||||
end
|
||||
end
|
||||
|
||||
defp value(list), do: Enum.find(list, &(to_string(&1) != ""))
|
||||
|
||||
def handle_error(%Plug.Conn{} = _conn, error) do
|
||||
error
|
||||
end
|
||||
|
||||
def auth_template, do: nil
|
||||
|
||||
def oauth_consumer_template, do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ defmodule Pleroma.Web.UserSocket do
|
|||
# performing token verification on connect.
|
||||
def connect(%{"token" => token}, socket) do
|
||||
with true <- Pleroma.Config.get([:chat, :enabled]),
|
||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
|
||||
%User{} = user <- Pleroma.Repo.get(User, user_id) do
|
||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600),
|
||||
%User{} = user <- Pleroma.User.get_by_id(user_id) do
|
||||
{:ok, assign(socket, :user_name, user.nickname)}
|
||||
else
|
||||
_e -> :error
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.ChatChannel do
|
||||
use Phoenix.Channel
|
||||
alias Pleroma.Web.ChatChannel.ChatChannelState
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ChatChannel.ChatChannelState
|
||||
|
||||
def join("chat:public", _message, socket) do
|
||||
send(self(), :after_join)
|
||||
|
|
@ -48,7 +48,7 @@ defmodule Pleroma.Web.ChatChannel.ChatChannelState do
|
|||
end)
|
||||
end
|
||||
|
||||
def messages() do
|
||||
def messages do
|
||||
Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse() end)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,14 +3,13 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.CommonAPI do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.ThreadMute
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Formatter
|
||||
|
||||
import Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
|
|
@ -27,10 +26,47 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def unfollow(follower, unfollowed) do
|
||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
||||
def accept_follow_request(follower, followed) do
|
||||
with {:ok, follower} <- User.maybe_follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Accept"
|
||||
}) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
||||
def reject_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Reject"
|
||||
}) do
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
||||
def delete(activity_id, user) do
|
||||
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
|
||||
%Object{} = object <- Object.normalize(object_id),
|
||||
true <- user.info.is_moderator || user.ap_id == object.data["actor"],
|
||||
with %Activity{data: %{"object" => _}} = activity <-
|
||||
Activity.get_by_id_with_object(activity_id),
|
||||
%Object{} = object <- Object.normalize(activity),
|
||||
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
||||
{:ok, _} <- unpin(activity_id, user),
|
||||
{:ok, delete} <- ActivityPub.delete(object) do
|
||||
{:ok, delete}
|
||||
|
|
@ -39,7 +75,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def repeat(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity.data["object"]["id"]),
|
||||
object <- Object.normalize(activity),
|
||||
nil <- Utils.get_existing_announce(user.ap_id, object) do
|
||||
ActivityPub.announce(user, object)
|
||||
else
|
||||
|
|
@ -50,7 +86,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def unrepeat(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||
object <- Object.normalize(activity) do
|
||||
ActivityPub.unannounce(user, object)
|
||||
else
|
||||
_ ->
|
||||
|
|
@ -60,7 +96,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def favorite(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity.data["object"]["id"]),
|
||||
object <- Object.normalize(activity),
|
||||
nil <- Utils.get_existing_like(user.ap_id, object) do
|
||||
ActivityPub.like(user, object)
|
||||
else
|
||||
|
|
@ -71,7 +107,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
def unfavorite(id_or_ap_id, user) do
|
||||
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
object <- Object.normalize(activity.data["object"]["id"]) do
|
||||
object <- Object.normalize(activity) do
|
||||
ActivityPub.unlike(user, object)
|
||||
else
|
||||
_ ->
|
||||
|
|
@ -88,8 +124,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
nil ->
|
||||
"public"
|
||||
|
||||
inReplyTo ->
|
||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
|
||||
in_reply_to ->
|
||||
Pleroma.Web.MastodonAPI.StatusView.get_visibility(in_reply_to.data["object"])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,15 +137,16 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
with status <- String.trim(status),
|
||||
attachments <- attachments_from_ids(data),
|
||||
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
|
||||
{content_html, mentions, tags} <-
|
||||
make_content_html(
|
||||
status,
|
||||
attachments,
|
||||
data
|
||||
data,
|
||||
visibility
|
||||
),
|
||||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
|
||||
context <- make_context(inReplyTo),
|
||||
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
|
||||
context <- make_context(in_reply_to),
|
||||
cw <- data["spoiler_text"],
|
||||
full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
|
||||
length when length in 1..limit <- String.length(full_payload),
|
||||
|
|
@ -120,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
context,
|
||||
content_html,
|
||||
attachments,
|
||||
inReplyTo,
|
||||
in_reply_to,
|
||||
tags,
|
||||
cw,
|
||||
cc
|
||||
|
|
@ -130,18 +167,21 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
object,
|
||||
"emoji",
|
||||
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
|
||||
|> Enum.reduce(%{}, fn {name, file}, acc ->
|
||||
|> Enum.reduce(%{}, fn {name, file, _}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
) do
|
||||
res =
|
||||
ActivityPub.create(%{
|
||||
to: to,
|
||||
actor: user,
|
||||
context: context,
|
||||
object: object,
|
||||
additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
|
||||
})
|
||||
ActivityPub.create(
|
||||
%{
|
||||
to: to,
|
||||
actor: user,
|
||||
context: context,
|
||||
object: object,
|
||||
additional: %{"cc" => cc, "directMessage" => visibility == "direct"}
|
||||
},
|
||||
Pleroma.Web.ControllerHelper.truthy_param?(data["preview"]) || false
|
||||
)
|
||||
|
||||
res
|
||||
end
|
||||
|
|
@ -248,14 +288,9 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
actor: user,
|
||||
account: account,
|
||||
statuses: statuses,
|
||||
content: content_html
|
||||
content: content_html,
|
||||
forward: data["forward"] || false
|
||||
}) do
|
||||
Enum.each(User.all_superusers(), fn superuser ->
|
||||
superuser
|
||||
|> Pleroma.AdminEmail.report(user, account, statuses, content_html)
|
||||
|> Pleroma.Mailer.deliver_async()
|
||||
end)
|
||||
|
||||
{:ok, activity}
|
||||
else
|
||||
{:error, err} -> {:error, err}
|
||||
|
|
@ -263,4 +298,24 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
{:account, nil} -> {:error, "Account not found"}
|
||||
end
|
||||
end
|
||||
|
||||
def hide_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
if ap_id not in user.info.muted_reblogs do
|
||||
info_changeset = User.Info.add_reblog_mute(user.info, ap_id)
|
||||
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
|
||||
User.update_and_set_cache(changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def show_reblogs(user, muted) do
|
||||
ap_id = muted.ap_id
|
||||
|
||||
if ap_id in user.info.muted_reblogs do
|
||||
info_changeset = User.Info.remove_reblog_mute(user.info, ap_id)
|
||||
changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset)
|
||||
User.update_and_set_cache(changeset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,24 +6,28 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
alias Calendar.Strftime
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
require Logger
|
||||
|
||||
# This is a hack for twidere.
|
||||
def get_by_id_or_ap_id(id) do
|
||||
activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
|
||||
activity =
|
||||
Activity.get_by_id_with_object(id) || Activity.get_create_by_object_ap_id_with_object(id)
|
||||
|
||||
activity &&
|
||||
if activity.data["type"] == "Create" do
|
||||
activity
|
||||
else
|
||||
Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||
Activity.get_create_by_object_ap_id_with_object(activity.data["object"])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,7 +105,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
def make_content_html(
|
||||
status,
|
||||
attachments,
|
||||
data
|
||||
data,
|
||||
visibility
|
||||
) do
|
||||
no_attachment_links =
|
||||
data
|
||||
|
|
@ -110,8 +115,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
content_type = get_content_type(data["content_type"])
|
||||
|
||||
options =
|
||||
if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do
|
||||
[safe_mention: true]
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
status
|
||||
|> format_input(content_type)
|
||||
|> format_input(content_type, options)
|
||||
|> maybe_add_attachments(attachments, no_attachment_links)
|
||||
|> maybe_add_nsfw_tag(data)
|
||||
end
|
||||
|
|
@ -231,15 +243,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
Strftime.strftime!(date, "%a %b %d %H:%M:%S %z %Y")
|
||||
end
|
||||
|
||||
def date_to_asctime(date) do
|
||||
with {:ok, date, _offset} <- date |> DateTime.from_iso8601() do
|
||||
def date_to_asctime(date) when is_binary(date) do
|
||||
with {:ok, date, _offset} <- DateTime.from_iso8601(date) do
|
||||
format_asctime(date)
|
||||
else
|
||||
_e ->
|
||||
Logger.warn("Date #{date} in wrong format, must be ISO 8601")
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def date_to_asctime(date) do
|
||||
Logger.warn("Date #{date} in wrong format, must be ISO 8601")
|
||||
""
|
||||
end
|
||||
|
||||
def to_masto_date(%NaiveDateTime{} = date) do
|
||||
date
|
||||
|> NaiveDateTime.to_iso8601()
|
||||
|
|
@ -266,7 +284,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
|
||||
def confirm_current_password(user, password) do
|
||||
with %User{local: true} = db_user <- Repo.get(User, user.id),
|
||||
with %User{local: true} = db_user <- User.get_by_id(user.id),
|
||||
true <- Pbkdf2.checkpw(password, db_user.password_hash) do
|
||||
{:ok, db_user}
|
||||
else
|
||||
|
|
@ -276,7 +294,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def emoji_from_profile(%{info: _info} = user) do
|
||||
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
|
||||
|> Enum.map(fn {shortcode, url} ->
|
||||
|> Enum.map(fn {shortcode, url, _} ->
|
||||
%{
|
||||
"type" => "Emoji",
|
||||
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
|
||||
|
|
@ -294,10 +312,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def maybe_notify_mentioned_recipients(
|
||||
recipients,
|
||||
%Activity{data: %{"to" => _to, "type" => type} = data} = _activity
|
||||
%Activity{data: %{"to" => _to, "type" => type} = data} = activity
|
||||
)
|
||||
when type == "Create" do
|
||||
object = Object.normalize(data["object"])
|
||||
object = Object.normalize(activity)
|
||||
|
||||
object_data =
|
||||
cond do
|
||||
|
|
@ -318,6 +336,24 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
|
||||
def maybe_notify_mentioned_recipients(recipients, _), do: recipients
|
||||
|
||||
def maybe_notify_subscribers(
|
||||
recipients,
|
||||
%Activity{data: %{"actor" => actor, "type" => type}} = activity
|
||||
)
|
||||
when type == "Create" do
|
||||
with %User{} = user <- User.get_cached_by_ap_id(actor) do
|
||||
subscriber_ids =
|
||||
user
|
||||
|> User.subscribers()
|
||||
|> Enum.filter(&Visibility.visible_for_user?(activity, &1))
|
||||
|> Enum.map(& &1.ap_id)
|
||||
|
||||
recipients ++ subscriber_ids
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_notify_subscribers(recipients, _), do: recipients
|
||||
|
||||
def maybe_extract_mentions(%{"tag" => tag}) do
|
||||
tag
|
||||
|> Enum.filter(fn x -> is_map(x) end)
|
||||
|
|
@ -344,4 +380,33 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end
|
||||
|
||||
def get_report_statuses(_, _), do: {:ok, nil}
|
||||
|
||||
# DEPRECATED mostly, context objects are now created at insertion time.
|
||||
def context_to_conversation_id(context) do
|
||||
with %Object{id: id} <- Object.get_cached_by_ap_id(context) do
|
||||
id
|
||||
else
|
||||
_e ->
|
||||
changeset = Object.context_mapping(context)
|
||||
|
||||
case Repo.insert(changeset) do
|
||||
{:ok, %{id: id}} ->
|
||||
id
|
||||
|
||||
# This should be solved by an upsert, but it seems ecto
|
||||
# has problems accessing the constraint inside the jsonb.
|
||||
{:error, _} ->
|
||||
Object.get_cached_by_ap_id(context).id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def conversation_id_to_context(id) do
|
||||
with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do
|
||||
context
|
||||
else
|
||||
_e ->
|
||||
{:error, "No such conversation"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,8 +5,14 @@
|
|||
defmodule Pleroma.Web.ControllerHelper do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
|
||||
@falsy_param_values [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"]
|
||||
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
|
||||
def truthy_param?(value), do: value not in @falsy_param_values
|
||||
|
||||
def oauth_scopes(params, default) do
|
||||
# Note: `scopes` is used by Mastodon — supporting it but sticking to OAuth's standard `scope` wherever we control it
|
||||
# Note: `scopes` is used by Mastodon — supporting it but sticking to
|
||||
# OAuth's standard `scope` wherever we control it
|
||||
Pleroma.Web.OAuth.parse_scopes(params["scope"] || params["scopes"], default)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ defmodule Pleroma.Web.Endpoint do
|
|||
at: "/",
|
||||
from: :pleroma,
|
||||
only:
|
||||
~w(index.html static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
|
||||
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc)
|
||||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
|
|
@ -50,11 +51,22 @@ defmodule Pleroma.Web.Endpoint do
|
|||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
|
||||
secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
|
||||
|
||||
cookie_name =
|
||||
if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
if secure_cookies,
|
||||
do: "__Host-pleroma_key",
|
||||
else: "pleroma_key"
|
||||
|
||||
same_site =
|
||||
if Pleroma.Config.oauth_consumer_enabled?() do
|
||||
# Note: "SameSite=Strict" prevents sign in with external OAuth provider
|
||||
# (there would be no cookies during callback request from OAuth provider)
|
||||
"SameSite=Lax"
|
||||
else
|
||||
"SameSite=Strict"
|
||||
end
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
|
|
@ -64,11 +76,30 @@ defmodule Pleroma.Web.Endpoint do
|
|||
key: cookie_name,
|
||||
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
||||
http_only: true,
|
||||
secure:
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
extra: "SameSite=Strict"
|
||||
secure: secure_cookies,
|
||||
extra: same_site
|
||||
)
|
||||
|
||||
# Note: the plug and its configuration is compile-time this can't be upstreamed yet
|
||||
if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do
|
||||
plug(RemoteIp, proxies: proxies)
|
||||
end
|
||||
|
||||
defmodule Instrumenter do
|
||||
use Prometheus.PhoenixInstrumenter
|
||||
end
|
||||
|
||||
defmodule PipelineInstrumenter do
|
||||
use Prometheus.PlugPipelineInstrumenter
|
||||
end
|
||||
|
||||
defmodule MetricsExporter do
|
||||
use Prometheus.PlugExporter
|
||||
end
|
||||
|
||||
plug(PipelineInstrumenter)
|
||||
plug(MetricsExporter)
|
||||
|
||||
plug(Pleroma.Web.Router)
|
||||
|
||||
@doc """
|
||||
|
|
|
|||
|
|
@ -5,65 +5,64 @@
|
|||
defmodule Pleroma.Web.Federator do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.Websub
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Jobs
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.Websub
|
||||
|
||||
require Logger
|
||||
|
||||
@websub Application.get_env(:pleroma, :websub)
|
||||
@ostatus Application.get_env(:pleroma, :ostatus)
|
||||
|
||||
def init() do
|
||||
def init do
|
||||
# 1 minute
|
||||
Process.sleep(1000 * 60 * 1)
|
||||
Process.sleep(1000 * 60)
|
||||
refresh_subscriptions()
|
||||
end
|
||||
|
||||
# Client API
|
||||
|
||||
def incoming_doc(doc) do
|
||||
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc])
|
||||
PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc])
|
||||
end
|
||||
|
||||
def incoming_ap_doc(params) do
|
||||
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params])
|
||||
PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params])
|
||||
end
|
||||
|
||||
def publish(activity, priority \\ 1) do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
||||
end
|
||||
|
||||
def publish_single_ap(params) do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
|
||||
end
|
||||
|
||||
def publish_single_websub(websub) do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
|
||||
end
|
||||
|
||||
def verify_websub(websub) do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
||||
end
|
||||
|
||||
def request_subscription(sub) do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub])
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub])
|
||||
end
|
||||
|
||||
def refresh_subscriptions() do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
||||
def refresh_subscriptions do
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
||||
end
|
||||
|
||||
def publish_single_salmon(params) do
|
||||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
||||
end
|
||||
|
||||
# Job Worker Callbacks
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
|
|||
{:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}}
|
||||
end
|
||||
|
||||
def start_link() do
|
||||
def start_link do
|
||||
enabled =
|
||||
if Mix.env() == :test, do: true, else: Pleroma.Config.get([__MODULE__, :enabled], false)
|
||||
|
||||
|
|
@ -39,11 +39,11 @@ defmodule Pleroma.Web.Federator.RetryQueue do
|
|||
GenServer.cast(__MODULE__, {:maybe_enqueue, data, transport, retries + 1})
|
||||
end
|
||||
|
||||
def get_stats() do
|
||||
def get_stats do
|
||||
GenServer.call(__MODULE__, :get_stats)
|
||||
end
|
||||
|
||||
def reset_stats() do
|
||||
def reset_stats do
|
||||
GenServer.call(__MODULE__, :reset_stats)
|
||||
end
|
||||
|
||||
|
|
@ -55,7 +55,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
|
|||
end
|
||||
end
|
||||
|
||||
def get_retry_timer_interval() do
|
||||
def get_retry_timer_interval do
|
||||
Pleroma.Config.get([:retry_queue, :interval], 1000)
|
||||
end
|
||||
|
||||
|
|
@ -231,7 +231,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
|
|||
end
|
||||
end
|
||||
|
||||
defp maybe_kickoff_timer() do
|
||||
defp maybe_kickoff_timer do
|
||||
GenServer.cast(__MODULE__, :kickoff_timer)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1 +1,58 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Pagination
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.User
|
||||
|
||||
def get_followers(user, params \\ %{}) do
|
||||
user
|
||||
|> User.get_followers_query()
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
def get_friends(user, params \\ %{}) do
|
||||
user
|
||||
|> User.get_friends_query()
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
def get_notifications(user, params \\ %{}) do
|
||||
options = cast_params(params)
|
||||
|
||||
user
|
||||
|> Notification.for_user_query()
|
||||
|> restrict(:exclude_types, options)
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
def get_scheduled_activities(user, params \\ %{}) do
|
||||
user
|
||||
|> ScheduledActivity.for_user_query()
|
||||
|> Pagination.fetch_paginated(params)
|
||||
end
|
||||
|
||||
defp cast_params(params) do
|
||||
param_types = %{
|
||||
exclude_types: {:array, :string}
|
||||
}
|
||||
|
||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||
changeset.changes
|
||||
end
|
||||
|
||||
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
|
||||
ap_types =
|
||||
mastodon_types
|
||||
|> Enum.map(&Activity.from_mastodon_notification_type/1)
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
query
|
||||
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
|
||||
end
|
||||
|
||||
defp restrict(query, _, _), do: query
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,30 +4,32 @@
|
|||
|
||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.Push
|
||||
alias Push.Subscription
|
||||
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.AppView
|
||||
alias Pleroma.Web.MastodonAPI.FilterView
|
||||
alias Pleroma.Web.MastodonAPI.ListView
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
alias Pleroma.Web.MastodonAPI.MastodonView
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
alias Pleroma.Web.MastodonAPI.ReportView
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
|
@ -53,16 +55,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||
false <- cs.changes[:client_name] == @local_mastodon_name,
|
||||
{:ok, app} <- Repo.insert(cs) do
|
||||
res = %{
|
||||
id: app.id |> to_string,
|
||||
name: app.client_name,
|
||||
client_id: app.client_id,
|
||||
client_secret: app.client_secret,
|
||||
redirect_uri: app.redirect_uris,
|
||||
website: app.website
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
conn
|
||||
|> put_view(AppView)
|
||||
|> render("show.json", %{app: app})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -134,8 +129,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
json(conn, account)
|
||||
end
|
||||
|
||||
def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
|
||||
with %User{} = user <- Repo.get(User, id),
|
||||
def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
|
||||
with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
|
||||
conn
|
||||
|> put_view(AppView)
|
||||
|> render("short.json", %{app: app})
|
||||
end
|
||||
end
|
||||
|
||||
def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id),
|
||||
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
|
||||
account = AccountView.render("account.json", %{user: user, for: for_user})
|
||||
json(conn, account)
|
||||
|
|
@ -163,6 +166,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
},
|
||||
stats: Stats.get_stats(),
|
||||
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
|
||||
languages: ["en"],
|
||||
registrations: Pleroma.Config.get([:instance, :registrations_open]),
|
||||
# Extra (not present in Mastodon):
|
||||
max_toot_chars: Keyword.get(instance, :limit)
|
||||
}
|
||||
|
||||
|
|
@ -175,14 +181,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
defp mastodonized_emoji do
|
||||
Pleroma.Emoji.get_all()
|
||||
|> Enum.map(fn {shortcode, relative_url} ->
|
||||
|> Enum.map(fn {shortcode, relative_url, tags} ->
|
||||
url = to_string(URI.merge(Web.base_url(), relative_url))
|
||||
|
||||
%{
|
||||
"shortcode" => shortcode,
|
||||
"static_url" => url,
|
||||
"visible_in_picker" => true,
|
||||
"url" => url
|
||||
"url" => url,
|
||||
"tags" => String.split(tags, ",")
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
|
@ -193,6 +200,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do
|
||||
params =
|
||||
conn.params
|
||||
|> Map.drop(["since_id", "max_id"])
|
||||
|> Map.merge(params)
|
||||
|
||||
last = List.last(activities)
|
||||
first = List.first(activities)
|
||||
|
||||
|
|
@ -277,7 +289,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
|
||||
with %User{} = user <- Repo.get(User, params["id"]) do
|
||||
with %User{} = user <- User.get_by_id(params["id"]) do
|
||||
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
|
||||
|
||||
conn
|
||||
|
|
@ -300,7 +312,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> Map.put(:visibility, "direct")
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities_query([user.ap_id], params)
|
||||
[user.ap_id]
|
||||
|> ActivityPub.fetch_activities_query(params)
|
||||
|> Repo.all()
|
||||
|
||||
conn
|
||||
|
|
@ -355,6 +368,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
|
||||
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
|
||||
conn
|
||||
|> add_link_headers(:scheduled_statuses, scheduled_activities)
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("index.json", %{scheduled_activities: scheduled_activities})
|
||||
end
|
||||
end
|
||||
|
||||
def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||
with %ScheduledActivity{} = scheduled_activity <-
|
||||
ScheduledActivity.get(user, scheduled_activity_id) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
else
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
def update_scheduled_status(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
%{"id" => scheduled_activity_id} = params
|
||||
) do
|
||||
with %ScheduledActivity{} = scheduled_activity <-
|
||||
ScheduledActivity.get(user, scheduled_activity_id),
|
||||
{:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
|
||||
with %ScheduledActivity{} = scheduled_activity <-
|
||||
ScheduledActivity.get(user, scheduled_activity_id),
|
||||
{:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
error -> error
|
||||
end
|
||||
end
|
||||
|
||||
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
||||
when length(media_ids) > 0 do
|
||||
params =
|
||||
|
|
@ -375,12 +437,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
_ -> Ecto.UUID.generate()
|
||||
end
|
||||
|
||||
{:ok, activity} =
|
||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
|
||||
scheduled_at = params["scheduled_at"]
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
|
||||
with {:ok, scheduled_activity} <-
|
||||
ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
|
||||
conn
|
||||
|> put_view(ScheduledActivityView)
|
||||
|> render("show.json", %{scheduled_activity: scheduled_activity})
|
||||
end
|
||||
else
|
||||
params = Map.drop(params, ["scheduled_at"])
|
||||
|
||||
{:ok, activity} =
|
||||
Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
|
||||
CommonAPI.post(user, params)
|
||||
end)
|
||||
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
end
|
||||
|
||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
|
|
@ -498,21 +575,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
result =
|
||||
notifications
|
||||
|> Enum.map(fn x -> render_notification(user, x) end)
|
||||
|> Enum.filter(& &1)
|
||||
notifications = MastodonAPI.get_notifications(user, params)
|
||||
|
||||
conn
|
||||
|> add_link_headers(:notifications, notifications)
|
||||
|> json(result)
|
||||
|> put_view(NotificationView)
|
||||
|> render("index.json", %{notifications: notifications, for: user})
|
||||
end
|
||||
|
||||
def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, notification} <- Notification.get(user, id) do
|
||||
json(conn, render_notification(user, notification))
|
||||
conn
|
||||
|> put_view(NotificationView)
|
||||
|> render("show.json", %{notification: notification, for: user})
|
||||
else
|
||||
{:error, reason} ->
|
||||
conn
|
||||
|
|
@ -649,9 +724,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||
end
|
||||
|
||||
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
|
||||
with %User{} = user <- Repo.get(User, id),
|
||||
{:ok, followers} <- User.get_followers(user) do
|
||||
def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||
with %User{} = user <- User.get_by_id(id),
|
||||
followers <- MastodonAPI.get_followers(user, params) do
|
||||
followers =
|
||||
cond do
|
||||
for_user && user.id == for_user.id -> followers
|
||||
|
|
@ -660,14 +735,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
conn
|
||||
|> add_link_headers(:followers, followers, user)
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: followers, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do
|
||||
with %User{} = user <- Repo.get(User, id),
|
||||
{:ok, followers} <- User.get_friends(user) do
|
||||
def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do
|
||||
with %User{} = user <- User.get_by_id(id),
|
||||
followers <- MastodonAPI.get_friends(user, params) do
|
||||
followers =
|
||||
cond do
|
||||
for_user && user.id == for_user.id -> followers
|
||||
|
|
@ -676,6 +752,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
conn
|
||||
|> add_link_headers(:following, followers, user)
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: followers, as: :user})
|
||||
end
|
||||
|
|
@ -690,17 +767,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||
with %User{} = follower <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.maybe_follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Accept"
|
||||
}) do
|
||||
with %User{} = follower <- User.get_by_id(id),
|
||||
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: followed, target: follower})
|
||||
|
|
@ -713,16 +781,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||
with %User{} = follower <- Repo.get(User, id),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Reject"
|
||||
}) do
|
||||
with %User{} = follower <- User.get_by_id(id),
|
||||
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: followed, target: follower})
|
||||
|
|
@ -735,12 +795,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||
with %User{} = followed <- Repo.get(User, id),
|
||||
with %User{} = followed <- User.get_by_id(id),
|
||||
false <- User.following?(follower, followed),
|
||||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: follower, target: followed})
|
||||
else
|
||||
true ->
|
||||
followed = User.get_cached_by_id(id)
|
||||
|
||||
{:ok, follower} =
|
||||
case conn.params["reblogs"] do
|
||||
true -> CommonAPI.show_reblogs(follower, followed)
|
||||
false -> CommonAPI.hide_reblogs(follower, followed)
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: follower, target: followed})
|
||||
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|
|
@ -749,7 +823,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
|
||||
with %User{} = followed <- Repo.get_by(User, nickname: uri),
|
||||
with %User{} = followed <- User.get_by_nickname(uri),
|
||||
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|
|
@ -763,9 +837,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||
with %User{} = followed <- Repo.get(User, id),
|
||||
{:ok, _activity} <- ActivityPub.unfollow(follower, followed),
|
||||
{:ok, follower, _} <- User.unfollow(follower, followed) do
|
||||
with %User{} = followed <- User.get_by_id(id),
|
||||
{:ok, follower} <- CommonAPI.unfollow(follower, followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: follower, target: followed})
|
||||
|
|
@ -773,7 +846,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||
with %User{} = muted <- Repo.get(User, id),
|
||||
with %User{} = muted <- User.get_by_id(id),
|
||||
{:ok, muter} <- User.mute(muter, muted) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|
|
@ -787,7 +860,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do
|
||||
with %User{} = muted <- Repo.get(User, id),
|
||||
with %User{} = muted <- User.get_by_id(id),
|
||||
{:ok, muter} <- User.unmute(muter, muted) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|
|
@ -808,7 +881,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
with %User{} = blocked <- User.get_by_id(id),
|
||||
{:ok, blocker} <- User.block(blocker, blocked),
|
||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
||||
conn
|
||||
|
|
@ -823,7 +896,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
|
||||
with %User{} = blocked <- Repo.get(User, id),
|
||||
with %User{} = blocked <- User.get_by_id(id),
|
||||
{:ok, blocker} <- User.unblock(blocker, blocked),
|
||||
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
|
||||
conn
|
||||
|
|
@ -858,6 +931,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
json(conn, %{})
|
||||
end
|
||||
|
||||
def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %User{} = subscription_target <- User.get_cached_by_id(id),
|
||||
{:ok, subscription_target} = User.subscribe(user, subscription_target) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: user, target: subscription_target})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %User{} = subscription_target <- User.get_cached_by_id(id),
|
||||
{:ok, subscription_target} = User.unsubscribe(user, subscription_target) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: user, target: subscription_target})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def status_search(user, query) do
|
||||
fetched =
|
||||
if Regex.match?(~r/https?:/, query) do
|
||||
|
|
@ -944,12 +1045,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||
activities =
|
||||
params =
|
||||
params
|
||||
|> Map.put("type", "Create")
|
||||
|> Map.put("favorited_by", user.ap_id)
|
||||
|> Map.put("blocking_user", user)
|
||||
|> ActivityPub.fetch_public_activities()
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities([], params)
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|
|
@ -959,7 +1062,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
||||
user = Repo.get(User, user.id)
|
||||
user = User.get_by_id(user.id)
|
||||
|
||||
activities =
|
||||
user.bookmarks
|
||||
|
|
@ -1016,7 +1119,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
accounts
|
||||
|> Enum.each(fn account_id ->
|
||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||
%User{} = followed <- Repo.get(User, account_id) do
|
||||
%User{} = followed <- User.get_by_id(account_id) do
|
||||
Pleroma.List.follow(list, followed)
|
||||
end
|
||||
end)
|
||||
|
|
@ -1028,7 +1131,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
accounts
|
||||
|> Enum.each(fn account_id ->
|
||||
with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
|
||||
%User{} = followed <- Repo.get(Pleroma.User, account_id) do
|
||||
%User{} = followed <- Pleroma.User.get_by_id(account_id) do
|
||||
Pleroma.List.unfollow(list, followed)
|
||||
end
|
||||
end)
|
||||
|
|
@ -1084,9 +1187,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def index(%{assigns: %{user: user}} = conn, _params) do
|
||||
token =
|
||||
conn
|
||||
|> get_session(:oauth_token)
|
||||
token = get_session(conn, :oauth_token)
|
||||
|
||||
if user && token do
|
||||
mastodon_emoji = mastodonized_emoji()
|
||||
|
|
@ -1114,7 +1215,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
auto_play_gif: false,
|
||||
display_sensitive_media: false,
|
||||
reduce_motion: false,
|
||||
max_toot_chars: limit
|
||||
max_toot_chars: limit,
|
||||
mascot: "/images/pleroma-fox-tan-smol.png"
|
||||
},
|
||||
rights: %{
|
||||
delete_others_notice: present?(user.info.is_moderator),
|
||||
|
|
@ -1123,7 +1225,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
compose: %{
|
||||
me: "#{user.id}",
|
||||
default_privacy: user.info.default_scope,
|
||||
default_sensitive: false
|
||||
default_sensitive: false,
|
||||
allow_content_types: Config.get([:instance, :allowed_post_formats])
|
||||
},
|
||||
media_attachments: %{
|
||||
accept_content_types: [
|
||||
|
|
@ -1185,6 +1288,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
|
||||
else
|
||||
conn
|
||||
|> put_session(:return_to, conn.request_path)
|
||||
|> redirect(to: "/web/login")
|
||||
end
|
||||
end
|
||||
|
|
@ -1241,16 +1345,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
"glitch"
|
||||
end
|
||||
|
||||
def login(conn, %{"code" => code}) do
|
||||
def login(%{assigns: %{user: %User{}}} = conn, _params) do
|
||||
redirect(conn, to: local_mastodon_root_path(conn))
|
||||
end
|
||||
|
||||
@doc "Local Mastodon FE login init action"
|
||||
def login(conn, %{"code" => auth_token}) do
|
||||
with {:ok, app} <- get_or_make_app(),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id),
|
||||
%Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
conn
|
||||
|> put_session(:oauth_token, token.token)
|
||||
|> redirect(to: "/web/getting-started")
|
||||
|> redirect(to: local_mastodon_root_path(conn))
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Local Mastodon FE callback action"
|
||||
def login(conn, _) do
|
||||
with {:ok, app} <- get_or_make_app() do
|
||||
path =
|
||||
|
|
@ -1263,12 +1373,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
scope: Enum.join(app.scopes, " ")
|
||||
)
|
||||
|
||||
conn
|
||||
|> redirect(to: path)
|
||||
redirect(conn, to: path)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_make_app() do
|
||||
defp local_mastodon_root_path(conn) do
|
||||
case get_session(conn, :return_to) do
|
||||
nil ->
|
||||
mastodon_api_path(conn, :index, ["getting-started"])
|
||||
|
||||
return_to ->
|
||||
delete_session(conn, :return_to)
|
||||
return_to
|
||||
end
|
||||
end
|
||||
|
||||
defp get_or_make_app do
|
||||
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
|
||||
scopes = ["read", "write", "follow", "push"]
|
||||
|
||||
|
|
@ -1304,7 +1424,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
Logger.debug("Unimplemented, returning unmodified relationship")
|
||||
|
||||
with %User{} = target <- Repo.get(User, id) do
|
||||
with %User{} = target <- User.get_by_id(id) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("relationship.json", %{user: user, target: target})
|
||||
|
|
@ -1321,45 +1441,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
json(conn, %{})
|
||||
end
|
||||
|
||||
def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do
|
||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||
|
||||
response = %{
|
||||
id: to_string(id),
|
||||
type: mastodon_type,
|
||||
created_at: CommonAPI.Utils.to_masto_date(created_at),
|
||||
account: AccountView.render("account.json", %{user: actor, for: user})
|
||||
}
|
||||
|
||||
case mastodon_type do
|
||||
"mention" ->
|
||||
response
|
||||
|> Map.merge(%{
|
||||
status: StatusView.render("status.json", %{activity: activity, for: user})
|
||||
})
|
||||
|
||||
"favourite" ->
|
||||
response
|
||||
|> Map.merge(%{
|
||||
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
|
||||
})
|
||||
|
||||
"reblog" ->
|
||||
response
|
||||
|> Map.merge(%{
|
||||
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
|
||||
})
|
||||
|
||||
"follow" ->
|
||||
response
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def get_filters(%{assigns: %{user: user}} = conn, _) do
|
||||
filters = Filter.get_filters(user)
|
||||
res = FilterView.render("filters.json", filters: filters)
|
||||
|
|
@ -1419,35 +1500,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
json(conn, %{})
|
||||
end
|
||||
|
||||
def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
|
||||
true = Push.enabled()
|
||||
Subscription.delete_if_exists(user, token)
|
||||
{:ok, subscription} = Subscription.create(user, token, params)
|
||||
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
|
||||
json(conn, view)
|
||||
# fallback action
|
||||
#
|
||||
def errors(conn, {:error, %Changeset{} = changeset}) do
|
||||
error_message =
|
||||
changeset
|
||||
|> Changeset.traverse_errors(fn {message, _opt} -> message end)
|
||||
|> Enum.map_join(", ", fn {_k, v} -> v end)
|
||||
|
||||
conn
|
||||
|> put_status(422)
|
||||
|> json(%{error: error_message})
|
||||
end
|
||||
|
||||
def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
|
||||
true = Push.enabled()
|
||||
subscription = Subscription.get(user, token)
|
||||
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
|
||||
json(conn, view)
|
||||
end
|
||||
|
||||
def update_push_subscription(
|
||||
%{assigns: %{user: user, token: token}} = conn,
|
||||
params
|
||||
) do
|
||||
true = Push.enabled()
|
||||
{:ok, subscription} = Subscription.update(user, token, params)
|
||||
view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
|
||||
json(conn, view)
|
||||
end
|
||||
|
||||
def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
|
||||
true = Push.enabled()
|
||||
{:ok, _response} = Subscription.delete(user, token)
|
||||
json(conn, %{})
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Record not found"})
|
||||
end
|
||||
|
||||
def errors(conn, _) do
|
||||
|
|
@ -1478,7 +1547,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
url,
|
||||
[],
|
||||
adapter: [
|
||||
timeout: timeout,
|
||||
recv_timeout: timeout,
|
||||
pool: :default
|
||||
]
|
||||
|
|
@ -1515,7 +1583,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
|
||||
with %Activity{} = activity <- Repo.get(Activity, status_id),
|
||||
with %Activity{} = activity <- Activity.get_by_id(status_id),
|
||||
true <- Visibility.visible_for_user?(activity, user) do
|
||||
data =
|
||||
StatusView.render(
|
||||
|
|
|
|||
71
lib/pleroma/web/mastodon_api/subscription_controller.ex
Normal file
71
lib/pleroma/web/mastodon_api/subscription_controller.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
|
||||
@moduledoc "The module represents functions to manage user subscriptions."
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Push.Subscription
|
||||
alias Pleroma.Web.MastodonAPI.PushSubscriptionView, as: View
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
# Creates PushSubscription
|
||||
# POST /api/v1/push/subscription
|
||||
#
|
||||
def create(%{assigns: %{user: user, token: token}} = conn, params) do
|
||||
with true <- Push.enabled(),
|
||||
{:ok, _} <- Subscription.delete_if_exists(user, token),
|
||||
{:ok, subscription} <- Subscription.create(user, token, params) do
|
||||
view = View.render("push_subscription.json", subscription: subscription)
|
||||
json(conn, view)
|
||||
end
|
||||
end
|
||||
|
||||
# Gets PushSubscription
|
||||
# GET /api/v1/push/subscription
|
||||
#
|
||||
def get(%{assigns: %{user: user, token: token}} = conn, _params) do
|
||||
with true <- Push.enabled(),
|
||||
{:ok, subscription} <- Subscription.get(user, token) do
|
||||
view = View.render("push_subscription.json", subscription: subscription)
|
||||
json(conn, view)
|
||||
end
|
||||
end
|
||||
|
||||
# Updates PushSubscription
|
||||
# PUT /api/v1/push/subscription
|
||||
#
|
||||
def update(%{assigns: %{user: user, token: token}} = conn, params) do
|
||||
with true <- Push.enabled(),
|
||||
{:ok, subscription} <- Subscription.update(user, token, params) do
|
||||
view = View.render("push_subscription.json", subscription: subscription)
|
||||
json(conn, view)
|
||||
end
|
||||
end
|
||||
|
||||
# Deletes PushSubscription
|
||||
# DELETE /api/v1/push/subscription
|
||||
#
|
||||
def delete(%{assigns: %{user: user, token: token}} = conn, _params) do
|
||||
with true <- Push.enabled(),
|
||||
{:ok, _response} <- Subscription.delete(user, token),
|
||||
do: json(conn, %{})
|
||||
end
|
||||
|
||||
# fallback action
|
||||
#
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json("Not found")
|
||||
end
|
||||
|
||||
def errors(conn, _) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json("Something went wrong")
|
||||
end
|
||||
end
|
||||
|
|
@ -53,9 +53,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
blocking: User.blocks?(user, target),
|
||||
muting: User.mutes?(user, target),
|
||||
muting_notifications: false,
|
||||
subscribing: User.subscribed_to?(user, target),
|
||||
requested: requested,
|
||||
domain_blocking: false,
|
||||
showing_reblogs: false,
|
||||
showing_reblogs: User.showing_reblogs?(user, target),
|
||||
endorsed: false
|
||||
}
|
||||
end
|
||||
|
|
@ -117,13 +118,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
},
|
||||
|
||||
# Pleroma extension
|
||||
pleroma: %{
|
||||
confirmation_pending: user_info.confirmation_pending,
|
||||
tags: user.tags,
|
||||
is_moderator: user.info.is_moderator,
|
||||
is_admin: user.info.is_admin,
|
||||
relationship: relationship
|
||||
}
|
||||
pleroma:
|
||||
%{
|
||||
confirmation_pending: user_info.confirmation_pending,
|
||||
tags: user.tags,
|
||||
is_moderator: user.info.is_moderator,
|
||||
is_admin: user.info.is_admin,
|
||||
relationship: relationship
|
||||
}
|
||||
|> with_notification_settings(user, opts[:for])
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -132,4 +135,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
end
|
||||
|
||||
defp username_from_nickname(_), do: nil
|
||||
|
||||
defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
Map.put(data, :notification_settings, user.info.notification_settings)
|
||||
end
|
||||
|
||||
defp with_notification_settings(data, _, _), do: data
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Web.MastodonAPI.Admin.AccountView
|
||||
|
||||
def render("index.json", %{users: users, count: count, page_size: page_size}) do
|
||||
%{
|
||||
users: render_many(users, AccountView, "show.json", as: :user),
|
||||
count: count,
|
||||
page_size: page_size
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{user: user}) do
|
||||
%{
|
||||
"id" => user.id,
|
||||
"nickname" => user.nickname,
|
||||
"deactivated" => user.info.deactivated
|
||||
}
|
||||
end
|
||||
end
|
||||
41
lib/pleroma/web/mastodon_api/views/app_view.ex
Normal file
41
lib/pleroma/web/mastodon_api/views/app_view.ex
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.AppView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Web.OAuth.App
|
||||
|
||||
@vapid_key :web_push_encryption
|
||||
|> Application.get_env(:vapid_details, [])
|
||||
|> Keyword.get(:public_key)
|
||||
|
||||
def render("show.json", %{app: %App{} = app}) do
|
||||
%{
|
||||
id: app.id |> to_string,
|
||||
name: app.client_name,
|
||||
client_id: app.client_id,
|
||||
client_secret: app.client_secret,
|
||||
redirect_uri: app.redirect_uris,
|
||||
website: app.website
|
||||
}
|
||||
|> with_vapid_key()
|
||||
end
|
||||
|
||||
def render("short.json", %{app: %App{website: webiste, client_name: name}}) do
|
||||
%{
|
||||
name: name,
|
||||
website: webiste
|
||||
}
|
||||
|> with_vapid_key()
|
||||
end
|
||||
|
||||
defp with_vapid_key(data) do
|
||||
if @vapid_key do
|
||||
Map.put(data, "vapid_key", @vapid_key)
|
||||
else
|
||||
data
|
||||
end
|
||||
end
|
||||
end
|
||||
64
lib/pleroma/web/mastodon_api/views/notification_view.ex
Normal file
64
lib/pleroma/web/mastodon_api/views/notification_view.ex
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.NotificationView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.NotificationView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("index.json", %{notifications: notifications, for: user}) do
|
||||
render_many(notifications, NotificationView, "show.json", %{for: user})
|
||||
end
|
||||
|
||||
def render("show.json", %{
|
||||
notification: %Notification{activity: activity} = notification,
|
||||
for: user
|
||||
}) do
|
||||
actor = User.get_cached_by_ap_id(activity.data["actor"])
|
||||
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||
mastodon_type = Activity.mastodon_notification_type(activity)
|
||||
|
||||
response = %{
|
||||
id: to_string(notification.id),
|
||||
type: mastodon_type,
|
||||
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
|
||||
account: AccountView.render("account.json", %{user: actor, for: user}),
|
||||
pleroma: %{
|
||||
is_seen: notification.seen
|
||||
}
|
||||
}
|
||||
|
||||
case mastodon_type do
|
||||
"mention" ->
|
||||
response
|
||||
|> Map.merge(%{
|
||||
status: StatusView.render("status.json", %{activity: activity, for: user})
|
||||
})
|
||||
|
||||
"favourite" ->
|
||||
response
|
||||
|> Map.merge(%{
|
||||
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
|
||||
})
|
||||
|
||||
"reblog" ->
|
||||
response
|
||||
|> Map.merge(%{
|
||||
status: StatusView.render("status.json", %{activity: parent_activity, for: user})
|
||||
})
|
||||
|
||||
"follow" ->
|
||||
response
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Web.Push
|
||||
|
||||
def render("push_subscription.json", %{subscription: subscription}) do
|
||||
%{
|
||||
|
|
@ -14,7 +15,5 @@ defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do
|
|||
}
|
||||
end
|
||||
|
||||
defp server_key do
|
||||
Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key)
|
||||
end
|
||||
defp server_key, do: Keyword.get(Push.vapid_config(), :public_key)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.ScheduledActivity
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("index.json", %{scheduled_activities: scheduled_activities}) do
|
||||
render_many(scheduled_activities, ScheduledActivityView, "show.json")
|
||||
end
|
||||
|
||||
def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
|
||||
%{
|
||||
id: to_string(scheduled_activity.id),
|
||||
scheduled_at: CommonAPI.Utils.to_masto_date(scheduled_activity.scheduled_at),
|
||||
params: status_params(scheduled_activity.params)
|
||||
}
|
||||
|> with_media_attachments(scheduled_activity)
|
||||
end
|
||||
|
||||
defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
|
||||
try do
|
||||
attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
|
||||
Map.put(data, :media_attachments, attachments)
|
||||
rescue
|
||||
_ -> data
|
||||
end
|
||||
end
|
||||
|
||||
defp with_media_attachments(data, _), do: data
|
||||
|
||||
defp status_params(params) do
|
||||
data = %{
|
||||
text: params["status"],
|
||||
sensitive: params["sensitive"],
|
||||
spoiler_text: params["spoiler_text"],
|
||||
visibility: params["visibility"],
|
||||
scheduled_at: params["scheduled_at"],
|
||||
poll: params["poll"],
|
||||
in_reply_to_id: params["in_reply_to_id"]
|
||||
}
|
||||
|
||||
data =
|
||||
if media_ids = params["media_ids"] do
|
||||
Map.put(data, :media_ids, media_ids)
|
||||
else
|
||||
data
|
||||
end
|
||||
|
||||
data
|
||||
end
|
||||
end
|
||||
|
|
@ -46,6 +46,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id),
|
||||
do: context_id
|
||||
|
||||
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
|
||||
do: Utils.context_to_conversation_id(context)
|
||||
|
||||
defp get_context_id(_), do: nil
|
||||
|
||||
def render("index.json", opts) do
|
||||
replied_to_activities = get_replied_to_activities(opts.activities)
|
||||
|
||||
|
|
@ -102,7 +110,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
website: nil
|
||||
},
|
||||
language: nil,
|
||||
emojis: []
|
||||
emojis: [],
|
||||
pleroma: %{
|
||||
local: activity.local
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -136,10 +147,37 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
content =
|
||||
object
|
||||
|> render_content()
|
||||
|> HTML.get_cached_scrubbed_html_for_object(
|
||||
|
||||
content_html =
|
||||
content
|
||||
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||
User.html_filter_policy(opts[:for]),
|
||||
activity,
|
||||
__MODULE__
|
||||
"mastoapi:content"
|
||||
)
|
||||
|
||||
content_plaintext =
|
||||
content
|
||||
|> HTML.get_cached_stripped_html_for_activity(
|
||||
activity,
|
||||
"mastoapi:content"
|
||||
)
|
||||
|
||||
summary = object["summary"] || ""
|
||||
|
||||
summary_html =
|
||||
summary
|
||||
|> HTML.get_cached_scrubbed_html_for_activity(
|
||||
User.html_filter_policy(opts[:for]),
|
||||
activity,
|
||||
"mastoapi:summary"
|
||||
)
|
||||
|
||||
summary_plaintext =
|
||||
summary
|
||||
|> HTML.get_cached_stripped_html_for_activity(
|
||||
activity,
|
||||
"mastoapi:summary"
|
||||
)
|
||||
|
||||
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
|
||||
|
|
@ -160,10 +198,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||
reblog: nil,
|
||||
card: card,
|
||||
content: content,
|
||||
content: content_html,
|
||||
created_at: created_at,
|
||||
reblogs_count: announcement_count,
|
||||
replies_count: 0,
|
||||
replies_count: object["repliesCount"] || 0,
|
||||
favourites_count: like_count,
|
||||
reblogged: present?(repeated),
|
||||
favourited: present?(favorited),
|
||||
|
|
@ -171,7 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
|
||||
pinned: pinned?(activity, user),
|
||||
sensitive: sensitive,
|
||||
spoiler_text: object["summary"] || "",
|
||||
spoiler_text: summary_html,
|
||||
visibility: get_visibility(object),
|
||||
media_attachments: attachments,
|
||||
mentions: mentions,
|
||||
|
|
@ -181,7 +219,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
website: nil
|
||||
},
|
||||
language: nil,
|
||||
emojis: build_emojis(activity.data["object"]["emoji"])
|
||||
emojis: build_emojis(activity.data["object"]["emoji"]),
|
||||
pleroma: %{
|
||||
local: activity.local,
|
||||
conversation_id: get_context_id(activity),
|
||||
content: %{"text/plain" => content_plaintext},
|
||||
spoiler_text: %{"text/plain" => summary_plaintext}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -251,7 +295,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
preview_url: href,
|
||||
text_url: href,
|
||||
type: type,
|
||||
description: attachment["name"]
|
||||
description: attachment["name"],
|
||||
pleroma: %{mime_type: media_type}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
||||
require Logger
|
||||
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@behaviour :cowboy_websocket
|
||||
|
||||
|
|
@ -90,7 +90,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
# Authenticated streams.
|
||||
defp allow_request(stream, {"access_token", access_token}) when stream in @streams do
|
||||
with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token),
|
||||
user = %User{} <- Repo.get(User, user_id) do
|
||||
user = %User{} <- User.get_by_id(user_id) do
|
||||
{:ok, user}
|
||||
else
|
||||
_ -> {:error, 403}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
else
|
||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||
|
||||
# Must preserve `%2F` for compatibility with S3 (https://git.pleroma.social/pleroma/pleroma/issues/580)
|
||||
# Must preserve `%2F` for compatibility with S3
|
||||
# https://git.pleroma.social/pleroma/pleroma/issues/580
|
||||
replacement = get_replacement(url, ":2F:")
|
||||
|
||||
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do
|
|||
|
||||
# TODO: Add additional properties to objects when we have the data available.
|
||||
# Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image
|
||||
# object when a Video or GIF is attached it will display that in the Whatsapp Rich Preview.
|
||||
# object when a Video or GIF is attached it will display that in Whatsapp Rich Preview.
|
||||
case media_type do
|
||||
"audio" ->
|
||||
[
|
||||
|
|
|
|||
|
|
@ -97,7 +97,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do
|
|||
| acc
|
||||
]
|
||||
|
||||
# TODO: Need the true width and height values here or Twitter renders an iFrame with a bad aspect ratio
|
||||
# TODO: Need the true width and height values here or Twitter renders an iFrame with
|
||||
# a bad aspect ratio
|
||||
"video" ->
|
||||
[
|
||||
{:meta, [property: "twitter:card", content: "player"], []},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright \xc2\xa9 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Metadata.Utils do
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do
|
||||
|
|
@ -12,19 +12,19 @@ defmodule Pleroma.Web.Metadata.Utils do
|
|||
# html content comes from DB already encoded, decode first and scrub after
|
||||
|> HtmlEntities.decode()
|
||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
|
||||
|> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|
||||
|> Formatter.demojify()
|
||||
|> Formatter.truncate()
|
||||
end
|
||||
|
||||
def scrub_html_and_truncate(content) when is_binary(content) do
|
||||
def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do
|
||||
content
|
||||
# html content comes from DB already encoded, decode first and scrub after
|
||||
|> HtmlEntities.decode()
|
||||
|> String.replace(~r/<br\s?\/?>/, " ")
|
||||
|> HTML.strip_tags()
|
||||
|> Formatter.demojify()
|
||||
|> Formatter.truncate()
|
||||
|> Formatter.truncate(max_length)
|
||||
end
|
||||
|
||||
def attachment_url(url) do
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Stats
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
|
|
@ -86,8 +85,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
end
|
||||
|
||||
staff_accounts =
|
||||
User.moderator_user_query()
|
||||
|> Repo.all()
|
||||
User.all_superusers()
|
||||
|> Enum.map(fn u -> u.ap_id end)
|
||||
|
||||
mrf_user_allowlist =
|
||||
|
|
@ -126,6 +124,9 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
end,
|
||||
if Keyword.get(instance, :allow_relay) do
|
||||
"relay"
|
||||
end,
|
||||
if Keyword.get(instance, :safe_dm_mentions) do
|
||||
"safe_dm_mentions"
|
||||
end
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@
|
|||
defmodule Pleroma.Web.OAuth.Authorization do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
|
@ -16,7 +16,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
schema "oauth_authorizations" do
|
||||
field(:token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime)
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
field(:used, :boolean, default: false)
|
||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
||||
belongs_to(:app, App)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,21 @@ defmodule Pleroma.Web.OAuth.FallbackController do
|
|||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Web.OAuth.OAuthController
|
||||
|
||||
# No user/password
|
||||
def call(conn, _) do
|
||||
def call(conn, {:register, :generic_error}) do
|
||||
conn
|
||||
|> put_status(:internal_server_error)
|
||||
|> put_flash(:error, "Unknown error, please check the details and try again.")
|
||||
|> OAuthController.registration_details(conn.params)
|
||||
end
|
||||
|
||||
def call(conn, {:register, _error}) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> put_flash(:error, "Invalid Username/Password")
|
||||
|> OAuthController.registration_details(conn.params)
|
||||
end
|
||||
|
||||
def call(conn, _error) do
|
||||
conn
|
||||
|> put_status(:unauthorized)
|
||||
|> put_flash(:error, "Invalid Username/Password")
|
||||
|
|
|
|||
|
|
@ -5,22 +5,46 @@
|
|||
defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Web.Auth.Authenticator
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Web.Auth.Authenticator
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
||||
|
||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||
|
||||
def authorize(conn, params) do
|
||||
def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do
|
||||
if ControllerHelper.truthy_param?(params["force_login"]) do
|
||||
do_authorize(conn, params)
|
||||
else
|
||||
redirect_uri =
|
||||
if is_binary(params["redirect_uri"]) do
|
||||
params["redirect_uri"]
|
||||
else
|
||||
app = Repo.preload(token, :app).app
|
||||
|
||||
app.redirect_uris
|
||||
|> String.split()
|
||||
|> Enum.at(0)
|
||||
end
|
||||
|
||||
redirect(conn, external: redirect_uri(conn, redirect_uri))
|
||||
end
|
||||
end
|
||||
|
||||
def authorize(conn, params), do: do_authorize(conn, params)
|
||||
|
||||
defp do_authorize(conn, params) do
|
||||
app = Repo.get_by(App, client_id: params["client_id"])
|
||||
available_scopes = (app && app.scopes) || []
|
||||
scopes = oauth_scopes(params, nil) || available_scopes
|
||||
|
|
@ -36,75 +60,73 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
})
|
||||
end
|
||||
|
||||
def create_authorization(conn, %{
|
||||
"authorization" =>
|
||||
%{
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = auth_params
|
||||
}) do
|
||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
scopes <- oauth_scopes(auth_params, []),
|
||||
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
||||
# Note: `scope` param is intentionally not optional in this context
|
||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
|
||||
redirect_uri =
|
||||
if redirect_uri == "." do
|
||||
# Special case: Local MastodonFE
|
||||
mastodon_api_url(conn, :login)
|
||||
def create_authorization(
|
||||
conn,
|
||||
%{"authorization" => auth_params} = params,
|
||||
opts \\ []
|
||||
) do
|
||||
with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do
|
||||
after_create_authorization(conn, auth, auth_params)
|
||||
else
|
||||
error ->
|
||||
handle_create_authorization_error(conn, error, auth_params)
|
||||
end
|
||||
end
|
||||
|
||||
def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do
|
||||
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||
|
||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
else
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
|
||||
url_params =
|
||||
if auth_params["state"] do
|
||||
Map.put(url_params, :state, auth_params["state"])
|
||||
else
|
||||
redirect_uri
|
||||
url_params
|
||||
end
|
||||
|
||||
cond do
|
||||
redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
|
||||
true ->
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
|
||||
url_params =
|
||||
if auth_params["state"] do
|
||||
Map.put(url_params, :state, auth_params["state"])
|
||||
else
|
||||
url_params
|
||||
end
|
||||
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
|
||||
redirect(conn, external: url)
|
||||
end
|
||||
else
|
||||
{scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
|
||||
conn
|
||||
|> put_flash(:error, "Permissions not specified.")
|
||||
|> put_status(:unauthorized)
|
||||
|> authorize(auth_params)
|
||||
|
||||
{:auth_active, false} ->
|
||||
conn
|
||||
|> put_flash(:error, "Account confirmation pending.")
|
||||
|> put_status(:forbidden)
|
||||
|> authorize(auth_params)
|
||||
|
||||
error ->
|
||||
Authenticator.handle_error(conn, error)
|
||||
redirect(conn, external: url)
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params)
|
||||
when scopes_issue in [:unsupported_scopes, :missing_scopes] do
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
|
||||
conn
|
||||
|> put_flash(:error, "This action is outside the authorized scopes")
|
||||
|> put_status(:unauthorized)
|
||||
|> authorize(auth_params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
||||
conn
|
||||
|> put_flash(:error, "Your login is missing a confirmed e-mail address")
|
||||
|> put_status(:forbidden)
|
||||
|> authorize(auth_params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(conn, error, _auth_params) do
|
||||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
with %App{} = app <- get_app_from_request(conn, params),
|
||||
fixed_token = fix_padding(params["code"]),
|
||||
%Authorization{} = auth <-
|
||||
Repo.get_by(Authorization, token: fixed_token, app_id: app.id),
|
||||
%User{} = user <- User.get_by_id(auth.user_id),
|
||||
{:ok, token} <- Token.exchange_token(app, auth),
|
||||
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
||||
response = %{
|
||||
|
|
@ -113,7 +135,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
refresh_token: token.refresh_token,
|
||||
created_at: DateTime.to_unix(inserted_at),
|
||||
expires_in: 60 * 10,
|
||||
scope: Enum.join(token.scopes, " ")
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
|
|
@ -126,12 +149,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|
||||
def token_exchange(
|
||||
conn,
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} = params
|
||||
%{"grant_type" => "password"} = params
|
||||
) do
|
||||
with %App{} = app <- get_app_from_request(conn, params),
|
||||
%User{} = user <- User.get_by_nickname_or_email(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
|
||||
%App{} = app <- get_app_from_request(conn, params),
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
|
||||
{:user_active, true} <- {:user_active, !user.info.deactivated},
|
||||
scopes <- oauth_scopes(params, app.scopes),
|
||||
[] <- scopes -- app.scopes,
|
||||
true <- Enum.any?(scopes),
|
||||
|
|
@ -142,15 +165,23 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: 60 * 10,
|
||||
scope: Enum.join(token.scopes, " ")
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
else
|
||||
{:auth_active, false} ->
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Account confirmation pending"})
|
||||
|> json(%{error: "Your login is missing a confirmed e-mail address"})
|
||||
|
||||
{:user_active, false} ->
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Your account is currently disabled"})
|
||||
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|
|
@ -182,6 +213,184 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||
def prepare_request(conn, %{"provider" => provider} = params) do
|
||||
scope =
|
||||
oauth_scopes(params, [])
|
||||
|> Enum.join(" ")
|
||||
|
||||
state =
|
||||
params
|
||||
|> Map.delete("scopes")
|
||||
|> Map.put("scope", scope)
|
||||
|> Poison.encode!()
|
||||
|
||||
params =
|
||||
params
|
||||
|> Map.drop(~w(scope scopes client_id redirect_uri))
|
||||
|> Map.put("state", state)
|
||||
|
||||
# Handing the request to Ueberauth
|
||||
redirect(conn, to: o_auth_path(conn, :request, provider, params))
|
||||
end
|
||||
|
||||
def request(conn, params) do
|
||||
message =
|
||||
if params["provider"] do
|
||||
"Unsupported OAuth provider: #{params["provider"]}."
|
||||
else
|
||||
"Bad OAuth request."
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_flash(:error, message)
|
||||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||
params = callback_params(params)
|
||||
messages = for e <- Map.get(failure, :errors, []), do: e.message
|
||||
message = Enum.join(messages, "; ")
|
||||
|
||||
conn
|
||||
|> put_flash(:error, "Failed to authenticate: #{message}.")
|
||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
|
||||
def callback(conn, params) do
|
||||
params = callback_params(params)
|
||||
|
||||
with {:ok, registration} <- Authenticator.get_registration(conn, params) do
|
||||
user = Repo.preload(registration, :user).user
|
||||
auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state))
|
||||
|
||||
if user do
|
||||
create_authorization(
|
||||
conn,
|
||||
%{"authorization" => auth_params},
|
||||
user: user
|
||||
)
|
||||
else
|
||||
registration_params =
|
||||
Map.merge(auth_params, %{
|
||||
"nickname" => Registration.nickname(registration),
|
||||
"email" => Registration.email(registration)
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> registration_details(registration_params)
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
conn
|
||||
|> put_flash(:error, "Failed to set up user account.")
|
||||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
end
|
||||
|
||||
defp callback_params(%{"state" => state} = params) do
|
||||
Map.merge(params, Poison.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(conn, params) do
|
||||
render(conn, "register.html", %{
|
||||
client_id: params["client_id"],
|
||||
redirect_uri: params["redirect_uri"],
|
||||
state: params["state"],
|
||||
scopes: oauth_scopes(params, []),
|
||||
nickname: params["nickname"],
|
||||
email: params["email"]
|
||||
})
|
||||
end
|
||||
|
||||
def register(conn, %{"op" => "connect"} = params) do
|
||||
authorization_params = Map.put(params, "name", params["auth_name"])
|
||||
create_authorization_params = %{"authorization" => authorization_params}
|
||||
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{_, {:ok, auth}} <-
|
||||
{:create_authorization, do_create_authorization(conn, create_authorization_params)},
|
||||
%User{} = user <- Repo.preload(auth, :user).user,
|
||||
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> after_create_authorization(auth, authorization_params)
|
||||
else
|
||||
{:create_authorization, error} ->
|
||||
{:register, handle_create_authorization_error(conn, error, create_authorization_params)}
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
def register(conn, %{"op" => "register"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
|
||||
conn
|
||||
|> put_session_registration_id(nil)
|
||||
|> create_authorization(
|
||||
%{
|
||||
"authorization" => %{
|
||||
"client_id" => params["client_id"],
|
||||
"redirect_uri" => params["redirect_uri"],
|
||||
"scopes" => oauth_scopes(params, nil)
|
||||
}
|
||||
},
|
||||
user: user
|
||||
)
|
||||
else
|
||||
{:error, changeset} ->
|
||||
message =
|
||||
Enum.map(changeset.errors, fn {field, {error, _}} ->
|
||||
"#{field} #{error}"
|
||||
end)
|
||||
|> Enum.join("; ")
|
||||
|
||||
message =
|
||||
String.replace(
|
||||
message,
|
||||
"ap_id has already been taken",
|
||||
"nickname has already been taken"
|
||||
)
|
||||
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> put_flash(:error, "Error: #{message}.")
|
||||
|> registration_details(params)
|
||||
|
||||
_ ->
|
||||
{:register, :generic_error}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_create_authorization(
|
||||
conn,
|
||||
%{
|
||||
"authorization" =>
|
||||
%{
|
||||
"client_id" => client_id,
|
||||
"redirect_uri" => redirect_uri
|
||||
} = auth_params
|
||||
} = params,
|
||||
user \\ nil
|
||||
) do
|
||||
with {_, {:ok, %User{} = user}} <-
|
||||
{:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
|
||||
%App{} = app <- Repo.get_by(App, client_id: client_id),
|
||||
true <- redirect_uri in String.split(app.redirect_uris),
|
||||
scopes <- oauth_scopes(auth_params, []),
|
||||
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
||||
# Note: `scope` param is intentionally not optional in this context
|
||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||
{:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
|
||||
Authorization.create_authorization(app, user, scopes)
|
||||
end
|
||||
end
|
||||
|
||||
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||
# decoding it. Investigate sometime.
|
||||
defp fix_padding(token) do
|
||||
|
|
@ -214,4 +423,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
nil
|
||||
end
|
||||
end
|
||||
|
||||
# Special case: Local MastodonFE
|
||||
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||
|
||||
defp redirect_uri(_conn, redirect_uri), do: redirect_uri
|
||||
|
||||
defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
|
||||
|
||||
defp put_session_registration_id(conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,17 +7,17 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
schema "oauth_tokens" do
|
||||
field(:token, :string)
|
||||
field(:refresh_token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
field(:valid_until, :naive_datetime)
|
||||
field(:valid_until, :naive_datetime_usec)
|
||||
belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId)
|
||||
belongs_to(:app, App)
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
def exchange_token(app, auth) do
|
||||
with {:ok, auth} <- Authorization.use_token(auth),
|
||||
true <- auth.app_id == app.id do
|
||||
create_token(app, Repo.get(User, auth.user_id), auth.scopes)
|
||||
create_token(app, User.get_by_id(auth.user_id), auth.scopes)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OStatus.UserRepresenter
|
||||
|
||||
require Logger
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.OStatus.FeedRepresenter do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||
alias Pleroma.Web.OStatus.UserRepresenter
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
|
||||
defmodule Pleroma.Web.OStatus.DeleteHandler do
|
||||
require Logger
|
||||
alias Pleroma.Web.XML
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.XML
|
||||
|
||||
def handle_delete(entry, _doc \\ nil) do
|
||||
with id <- XML.string_from_xpath("//id", entry),
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OStatus.FollowHandler do
|
||||
alias Pleroma.Web.XML
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.XML
|
||||
|
||||
def handle(entry, doc) do
|
||||
with {:ok, actor} <- OStatus.find_make_or_update_user(doc),
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue