Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
This commit is contained in:
commit
e82e73478e
228 changed files with 5490 additions and 1366 deletions
25
lib/mix/tasks/benchmark.ex
Normal file
25
lib/mix/tasks/benchmark.ex
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
defmodule Mix.Tasks.Pleroma.Benchmark do
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
|
||||
def run(["search"]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
Benchee.run(%{
|
||||
"search" => fn ->
|
||||
Pleroma.Web.MastodonAPI.MastodonAPIController.status_search(nil, "cofe")
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
def run(["tag"]) do
|
||||
Common.start_pleroma()
|
||||
|
||||
Benchee.run(%{
|
||||
"tag" => fn ->
|
||||
%{"type" => "Create", "tag" => "cofe"}
|
||||
|> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()
|
||||
end
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -109,7 +109,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
])
|
||||
)
|
||||
|
||||
binary_archive = Tesla.get!(src_url).body
|
||||
binary_archive = Tesla.get!(client(), src_url).body
|
||||
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||
|
||||
sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright]
|
||||
|
|
@ -137,7 +137,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
])
|
||||
)
|
||||
|
||||
files = Tesla.get!(files_url).body |> Poison.decode!()
|
||||
files = Tesla.get!(client(), files_url).body |> Jason.decode!()
|
||||
|
||||
IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
IO.puts("Downloading the pack and generating SHA256")
|
||||
|
||||
binary_archive = Tesla.get!(src).body
|
||||
binary_archive = Tesla.get!(client(), src).body
|
||||
archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16()
|
||||
|
||||
IO.puts("SHA256 is #{archive_sha}")
|
||||
|
|
@ -239,7 +239,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts)
|
||||
|
||||
File.write!(files_name, Poison.encode!(emoji_map, pretty: true))
|
||||
File.write!(files_name, Jason.encode!(emoji_map, pretty: true))
|
||||
|
||||
IO.puts("""
|
||||
|
||||
|
|
@ -248,11 +248,11 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
""")
|
||||
|
||||
if File.exists?("index.json") do
|
||||
existing_data = File.read!("index.json") |> Poison.decode!()
|
||||
existing_data = File.read!("index.json") |> Jason.decode!()
|
||||
|
||||
File.write!(
|
||||
"index.json",
|
||||
Poison.encode!(
|
||||
Jason.encode!(
|
||||
Map.merge(
|
||||
existing_data,
|
||||
pack_json
|
||||
|
|
@ -263,16 +263,16 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
IO.puts("index.json file has been update with the #{name} pack")
|
||||
else
|
||||
File.write!("index.json", Poison.encode!(pack_json, pretty: true))
|
||||
File.write!("index.json", Jason.encode!(pack_json, pretty: true))
|
||||
|
||||
IO.puts("index.json has been created with the #{name} pack")
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_manifest(from) do
|
||||
Poison.decode!(
|
||||
Jason.decode!(
|
||||
if String.starts_with?(from, "http") do
|
||||
Tesla.get!(from).body
|
||||
Tesla.get!(client(), from).body
|
||||
else
|
||||
File.read!(from)
|
||||
end
|
||||
|
|
@ -290,4 +290,12 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
]
|
||||
)
|
||||
end
|
||||
|
||||
defp client do
|
||||
middleware = [
|
||||
{Tesla.Middleware.FollowRedirects, [max_redirects: 3]}
|
||||
]
|
||||
|
||||
Tesla.client(middleware)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
proceed? = assume_yes? or Mix.shell().yes?("Continue?")
|
||||
|
||||
unless not proceed? do
|
||||
if proceed? do
|
||||
Common.start_pleroma()
|
||||
|
||||
params = %{
|
||||
|
|
@ -138,7 +138,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
bio: bio
|
||||
}
|
||||
|
||||
changeset = User.register_changeset(%User{}, params, confirmed: true)
|
||||
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
|
||||
{:ok, _user} = User.register(changeset)
|
||||
|
||||
Mix.shell().info("User #{nickname} created")
|
||||
|
|
@ -163,7 +163,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
Common.start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
User.delete(user)
|
||||
User.perform(:delete, user)
|
||||
Mix.shell().info("User #{nickname} deleted.")
|
||||
else
|
||||
_ ->
|
||||
|
|
@ -380,7 +380,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
Common.start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
User.delete_user_activities(user)
|
||||
{:ok, _} = User.delete_user_activities(user)
|
||||
Mix.shell().info("User #{nickname} statuses deleted.")
|
||||
else
|
||||
_ ->
|
||||
|
|
|
|||
|
|
@ -6,14 +6,18 @@ defmodule Pleroma.Activity do
|
|||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
@type actor :: String.t()
|
||||
|
||||
@primary_key {:id, Pleroma.FlakeId, autogenerate: true}
|
||||
|
||||
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
|
||||
|
|
@ -33,6 +37,8 @@ defmodule Pleroma.Activity do
|
|||
field(:local, :boolean, default: true)
|
||||
field(:actor, :string)
|
||||
field(:recipients, {:array, :string}, default: [])
|
||||
# This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
|
||||
has_one(:bookmark, Bookmark)
|
||||
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!
|
||||
|
|
@ -71,6 +77,16 @@ defmodule Pleroma.Activity do
|
|||
|> preload([activity, object], object: object)
|
||||
end
|
||||
|
||||
def with_preloaded_bookmark(query, %User{} = user) do
|
||||
from([a] in query,
|
||||
left_join: b in Bookmark,
|
||||
on: b.user_id == ^user.id and b.activity_id == a.id,
|
||||
preload: [bookmark: b]
|
||||
)
|
||||
end
|
||||
|
||||
def with_preloaded_bookmark(query, _), do: query
|
||||
|
||||
def get_by_ap_id(ap_id) do
|
||||
Repo.one(
|
||||
from(
|
||||
|
|
@ -80,6 +96,16 @@ defmodule Pleroma.Activity do
|
|||
)
|
||||
end
|
||||
|
||||
def get_bookmark(%Activity{} = activity, %User{} = user) do
|
||||
if Ecto.assoc_loaded?(activity.bookmark) do
|
||||
activity.bookmark
|
||||
else
|
||||
Bookmark.get(user.id, activity.id)
|
||||
end
|
||||
end
|
||||
|
||||
def get_bookmark(_, _), do: nil
|
||||
|
||||
def change(struct, params \\ %{}) do
|
||||
struct
|
||||
|> cast(params, [:data])
|
||||
|
|
@ -260,4 +286,32 @@ defmodule Pleroma.Activity do
|
|||
|> where([s], s.actor == ^actor)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
|
||||
from(
|
||||
a in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"? ->> 'type' = 'Follow'",
|
||||
a.data
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"? ->> 'state' = 'pending'",
|
||||
a.data
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
a.data,
|
||||
a.data,
|
||||
^ap_id
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@spec query_by_actor(actor()) :: Ecto.Query.t()
|
||||
def query_by_actor(actor) do
|
||||
from(a in Activity, where: a.actor == ^actor)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
16
lib/pleroma/bbs/authenticator.ex
Normal file
16
lib/pleroma/bbs/authenticator.ex
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
defmodule Pleroma.BBS.Authenticator do
|
||||
use Sshd.PasswordAuthenticator
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.User
|
||||
|
||||
def authenticate(username, password) do
|
||||
username = to_string(username)
|
||||
password = to_string(password)
|
||||
|
||||
with %User{} = user <- User.get_by_nickname(username) do
|
||||
Pbkdf2.checkpw(password, user.password_hash)
|
||||
else
|
||||
_e -> false
|
||||
end
|
||||
end
|
||||
end
|
||||
147
lib/pleroma/bbs/handler.ex
Normal file
147
lib/pleroma/bbs/handler.ex
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
defmodule Pleroma.BBS.Handler do
|
||||
use Sshd.ShellHandler
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
def on_shell(username, _pubkey, _ip, _port) do
|
||||
:ok = IO.puts("Welcome to #{Pleroma.Config.get([:instance, :name])}!")
|
||||
user = Pleroma.User.get_cached_by_nickname(to_string(username))
|
||||
Logger.debug("#{inspect(user)}")
|
||||
loop(run_state(user: user))
|
||||
end
|
||||
|
||||
def on_connect(username, ip, port, method) do
|
||||
Logger.debug(fn ->
|
||||
"""
|
||||
Incoming SSH shell #{inspect(self())} requested for #{username} from #{inspect(ip)}:#{
|
||||
inspect(port)
|
||||
} using #{inspect(method)}
|
||||
"""
|
||||
end)
|
||||
end
|
||||
|
||||
def on_disconnect(username, ip, port) do
|
||||
Logger.debug(fn ->
|
||||
"Disconnecting SSH shell for #{username} from #{inspect(ip)}:#{inspect(port)}"
|
||||
end)
|
||||
end
|
||||
|
||||
defp loop(state) do
|
||||
self_pid = self()
|
||||
counter = state.counter
|
||||
prefix = state.prefix
|
||||
user = state.user
|
||||
|
||||
input = spawn(fn -> io_get(self_pid, prefix, counter, user.nickname) end)
|
||||
wait_input(state, input)
|
||||
end
|
||||
|
||||
def puts_activity(activity) do
|
||||
status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity})
|
||||
IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")
|
||||
IO.puts(HtmlSanitizeEx.strip_tags(status.content))
|
||||
IO.puts("")
|
||||
end
|
||||
|
||||
def handle_command(state, "help") do
|
||||
IO.puts("Available commands:")
|
||||
IO.puts("help - This help")
|
||||
IO.puts("home - Show the home timeline")
|
||||
IO.puts("p <text> - Post the given text")
|
||||
IO.puts("r <id> <text> - Reply to the post with the given id")
|
||||
IO.puts("quit - Quit")
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "r " <> text) do
|
||||
text = String.trim(text)
|
||||
[activity_id, rest] = String.split(text, " ", parts: 2)
|
||||
|
||||
with %Activity{} <- Activity.get_by_id(activity_id),
|
||||
{:ok, _activity} <-
|
||||
CommonAPI.post(user, %{"status" => rest, "in_reply_to_status_id" => activity_id}) do
|
||||
IO.puts("Replied!")
|
||||
else
|
||||
_e -> IO.puts("Could not reply...")
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(%{user: user} = state, "p " <> text) do
|
||||
text = String.trim(text)
|
||||
|
||||
with {:ok, _activity} <- CommonAPI.post(user, %{"status" => text}) do
|
||||
IO.puts("Posted!")
|
||||
else
|
||||
_e -> IO.puts("Could not post...")
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(state, "home") do
|
||||
user = state.user
|
||||
|
||||
params =
|
||||
%{}
|
||||
|> Map.put("type", ["Create"])
|
||||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("muting_user", user)
|
||||
|> Map.put("user", user)
|
||||
|
||||
activities =
|
||||
[user.ap_id | user.following]
|
||||
|> ActivityPub.fetch_activities(params)
|
||||
|> ActivityPub.contain_timeline(user)
|
||||
|
||||
Enum.each(activities, fn activity ->
|
||||
puts_activity(activity)
|
||||
end)
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
def handle_command(state, command) do
|
||||
IO.puts("Unknown command '#{command}'")
|
||||
state
|
||||
end
|
||||
|
||||
defp wait_input(state, input) do
|
||||
receive do
|
||||
{:input, ^input, "quit\n"} ->
|
||||
IO.puts("Exiting...")
|
||||
|
||||
{:input, ^input, code} when is_binary(code) ->
|
||||
code = String.trim(code)
|
||||
|
||||
state = handle_command(state, code)
|
||||
|
||||
loop(%{state | counter: state.counter + 1})
|
||||
|
||||
{:error, :interrupted} ->
|
||||
IO.puts("Caught Ctrl+C...")
|
||||
loop(%{state | counter: state.counter + 1})
|
||||
|
||||
{:input, ^input, msg} ->
|
||||
:ok = Logger.warn("received unknown message: #{inspect(msg)}")
|
||||
loop(%{state | counter: state.counter + 1})
|
||||
end
|
||||
end
|
||||
|
||||
defp run_state(opts) do
|
||||
%{prefix: "pleroma", counter: 1, user: opts[:user]}
|
||||
end
|
||||
|
||||
defp io_get(pid, prefix, counter, username) do
|
||||
prompt = prompt(prefix, counter, username)
|
||||
send(pid, {:input, self(), IO.gets(:stdio, prompt)})
|
||||
end
|
||||
|
||||
defp prompt(prefix, counter, username) do
|
||||
prompt = "#{username}@#{prefix}:#{counter}>"
|
||||
prompt <> " "
|
||||
end
|
||||
end
|
||||
60
lib/pleroma/bookmark.ex
Normal file
60
lib/pleroma/bookmark.ex
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
defmodule Pleroma.Bookmark do
|
||||
use Ecto.Schema
|
||||
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.FlakeId
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "bookmarks" do
|
||||
belongs_to(:user, User, type: FlakeId)
|
||||
belongs_to(:activity, Activity, type: FlakeId)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
def create(user_id, activity_id) do
|
||||
attrs = %{
|
||||
user_id: user_id,
|
||||
activity_id: activity_id
|
||||
}
|
||||
|
||||
%Bookmark{}
|
||||
|> cast(attrs, [:user_id, :activity_id])
|
||||
|> validate_required([:user_id, :activity_id])
|
||||
|> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec for_user_query(FlakeId.t()) :: Ecto.Query.t()
|
||||
def for_user_query(user_id) do
|
||||
Bookmark
|
||||
|> where(user_id: ^user_id)
|
||||
|> join(:inner, [b], activity in assoc(b, :activity))
|
||||
|> preload([b, a], activity: a)
|
||||
end
|
||||
|
||||
def get(user_id, activity_id) do
|
||||
Bookmark
|
||||
|> where(user_id: ^user_id)
|
||||
|> where(activity_id: ^activity_id)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()}
|
||||
def destroy(user_id, activity_id) do
|
||||
from(b in Bookmark,
|
||||
where: b.user_id == ^user_id,
|
||||
where: b.activity_id == ^activity_id
|
||||
)
|
||||
|> Repo.one()
|
||||
|> Repo.delete()
|
||||
end
|
||||
end
|
||||
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Captcha.Kocaptcha do
|
|||
%{error: "Kocaptcha service unavailable"}
|
||||
|
||||
{:ok, res} ->
|
||||
json_resp = Poison.decode!(res.body)
|
||||
json_resp = Jason.decode!(res.body)
|
||||
|
||||
%{
|
||||
type: :kocaptcha,
|
||||
|
|
|
|||
75
lib/pleroma/conversation.ex
Normal file
75
lib/pleroma/conversation.ex
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Conversation do
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "conversations" do
|
||||
# This is the context ap id.
|
||||
field(:ap_id, :string)
|
||||
has_many(:participations, Participation)
|
||||
has_many(:users, through: [:participations, :user])
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def creation_cng(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:ap_id])
|
||||
|> validate_required([:ap_id])
|
||||
|> unique_constraint(:ap_id)
|
||||
end
|
||||
|
||||
def create_for_ap_id(ap_id) do
|
||||
%__MODULE__{}
|
||||
|> creation_cng(%{ap_id: ap_id})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [updated_at: NaiveDateTime.utc_now()]],
|
||||
returning: true,
|
||||
conflict_target: :ap_id
|
||||
)
|
||||
end
|
||||
|
||||
def get_for_ap_id(ap_id) do
|
||||
Repo.get_by(__MODULE__, ap_id: ap_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
This will
|
||||
1. Create a conversation if there isn't one already
|
||||
2. Create a participation for all the people involved who don't have one already
|
||||
3. Bump all relevant participations to 'unread'
|
||||
"""
|
||||
def create_or_bump_for(activity) do
|
||||
with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),
|
||||
"Create" <- activity.data["type"],
|
||||
object <- Pleroma.Object.normalize(activity),
|
||||
"Note" <- object.data["type"],
|
||||
ap_id when is_binary(ap_id) and byte_size(ap_id) > 0 <- object.data["context"] do
|
||||
{:ok, conversation} = create_for_ap_id(ap_id)
|
||||
|
||||
users = User.get_users_from_set(activity.recipients, false)
|
||||
|
||||
participations =
|
||||
Enum.map(users, fn user ->
|
||||
{:ok, participation} =
|
||||
Participation.create_for_user_and_conversation(user, conversation)
|
||||
|
||||
participation
|
||||
end)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
conversation
|
||||
| participations: participations
|
||||
}}
|
||||
else
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
81
lib/pleroma/conversation/participation.ex
Normal file
81
lib/pleroma/conversation/participation.ex
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Conversation.Participation do
|
||||
use Ecto.Schema
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
schema "conversation_participations" do
|
||||
belongs_to(:user, User, type: Pleroma.FlakeId)
|
||||
belongs_to(:conversation, Conversation)
|
||||
field(:read, :boolean, default: false)
|
||||
field(:last_activity_id, Pleroma.FlakeId, virtual: true)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def creation_cng(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:user_id, :conversation_id])
|
||||
|> validate_required([:user_id, :conversation_id])
|
||||
end
|
||||
|
||||
def create_for_user_and_conversation(user, conversation) do
|
||||
%__MODULE__{}
|
||||
|> creation_cng(%{user_id: user.id, conversation_id: conversation.id})
|
||||
|> Repo.insert(
|
||||
on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]],
|
||||
returning: true,
|
||||
conflict_target: [:user_id, :conversation_id]
|
||||
)
|
||||
end
|
||||
|
||||
def read_cng(struct, params) do
|
||||
struct
|
||||
|> cast(params, [:read])
|
||||
|> validate_required([:read])
|
||||
end
|
||||
|
||||
def mark_as_read(participation) do
|
||||
participation
|
||||
|> read_cng(%{read: true})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def mark_as_unread(participation) do
|
||||
participation
|
||||
|> read_cng(%{read: false})
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def for_user(user, params \\ %{}) do
|
||||
from(p in __MODULE__,
|
||||
where: p.user_id == ^user.id,
|
||||
order_by: [desc: p.updated_at]
|
||||
)
|
||||
|> Pleroma.Pagination.fetch_paginated(params)
|
||||
|> Repo.preload(conversation: [:users])
|
||||
end
|
||||
|
||||
def for_user_with_last_activity_id(user, params \\ %{}) do
|
||||
for_user(user, params)
|
||||
|> Enum.map(fn participation ->
|
||||
activity_id =
|
||||
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||
"user" => user,
|
||||
"blocking_user" => user
|
||||
})
|
||||
|
||||
%{
|
||||
participation
|
||||
| last_activity_id: activity_id
|
||||
}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -113,9 +113,7 @@ defmodule Pleroma.Formatter do
|
|||
|
||||
html =
|
||||
if not strip do
|
||||
"<img height='32px' width='32px' alt='#{emoji}' title='#{emoji}' src='#{
|
||||
MediaProxy.url(file)
|
||||
}' />"
|
||||
"<img class='emoji' alt='#{emoji}' title='#{emoji}' src='#{MediaProxy.url(file)}' />"
|
||||
else
|
||||
""
|
||||
end
|
||||
|
|
@ -130,12 +128,23 @@ defmodule Pleroma.Formatter do
|
|||
|
||||
def demojify(text, nil), do: text
|
||||
|
||||
@doc "Outputs a list of the emoji-shortcodes in a text"
|
||||
def get_emoji(text) when is_binary(text) do
|
||||
Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
|
||||
end
|
||||
|
||||
def get_emoji(_), do: []
|
||||
|
||||
@doc "Outputs a list of the emoji-Maps in a text"
|
||||
def get_emoji_map(text) when is_binary(text) do
|
||||
get_emoji(text)
|
||||
|> Enum.reduce(%{}, fn {name, file, _group}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
end
|
||||
|
||||
def get_emoji_map(_), do: []
|
||||
|
||||
def html_escape({text, mentions, hashtags}, type) do
|
||||
{html_escape(text, type), mentions, hashtags}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,12 +28,18 @@ 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_activity(content, scrubbers, activity, key \\ "") do
|
||||
def get_cached_scrubbed_html_for_activity(
|
||||
content,
|
||||
scrubbers,
|
||||
activity,
|
||||
key \\ "",
|
||||
callback \\ fn x -> x end
|
||||
) do
|
||||
key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
|
||||
|
||||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
object = Pleroma.Object.normalize(activity)
|
||||
ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false)
|
||||
ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false, callback)
|
||||
end)
|
||||
end
|
||||
|
||||
|
|
@ -42,24 +48,27 @@ defmodule Pleroma.HTML do
|
|||
content,
|
||||
HtmlSanitizeEx.Scrubber.StripTags,
|
||||
activity,
|
||||
key
|
||||
key,
|
||||
&HtmlEntities.decode/1
|
||||
)
|
||||
end
|
||||
|
||||
def ensure_scrubbed_html(
|
||||
content,
|
||||
scrubbers,
|
||||
false = _fake
|
||||
fake,
|
||||
callback
|
||||
) do
|
||||
{:commit, filter_tags(content, scrubbers)}
|
||||
end
|
||||
content =
|
||||
content
|
||||
|> filter_tags(scrubbers)
|
||||
|> callback.()
|
||||
|
||||
def ensure_scrubbed_html(
|
||||
content,
|
||||
scrubbers,
|
||||
true = _fake
|
||||
) do
|
||||
{:ignore, filter_tags(content, scrubbers)}
|
||||
if fake do
|
||||
{:ignore, content}
|
||||
else
|
||||
{:commit, content}
|
||||
end
|
||||
end
|
||||
|
||||
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
|
||||
|
|
@ -142,6 +151,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
|
|||
Meta.allow_tag_with_these_attributes("img", [
|
||||
"width",
|
||||
"height",
|
||||
"class",
|
||||
"title",
|
||||
"alt"
|
||||
])
|
||||
|
|
@ -212,6 +222,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
|
|||
Meta.allow_tag_with_these_attributes("img", [
|
||||
"width",
|
||||
"height",
|
||||
"class",
|
||||
"title",
|
||||
"alt"
|
||||
])
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
defmodule Pleroma.Object.Containment do
|
||||
@moduledoc """
|
||||
# Object Containment
|
||||
|
||||
This module contains some useful functions for containing objects to specific
|
||||
origins and determining those origins. They previously lived in the
|
||||
ActivityPub `Transmogrifier` module.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
defp csp_string do
|
||||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
|
||||
static_url = Pleroma.Web.Endpoint.static_url()
|
||||
websocket_url = String.replace(static_url, "http", "ws")
|
||||
websocket_url = Pleroma.Web.Endpoint.websocket_url()
|
||||
|
||||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
|
||||
|
|
@ -16,14 +17,45 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
|
||||
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
|
||||
|
||||
def call(conn, _) do
|
||||
with {:ok, token_str} <- fetch_token_str(conn),
|
||||
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
|
||||
def call(%{params: %{"access_token" => access_token}} = conn, _) do
|
||||
with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
|
||||
conn
|
||||
|> assign(:token, token_record)
|
||||
|> assign(:user, user)
|
||||
else
|
||||
_ -> conn
|
||||
_ ->
|
||||
# token found, but maybe only with app
|
||||
with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
|
||||
conn
|
||||
|> assign(:token, token_record)
|
||||
|> assign(:app, app)
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def call(conn, _) do
|
||||
case fetch_token_str(conn) do
|
||||
{:ok, token} ->
|
||||
with {:ok, user, token_record} <- fetch_user_and_token(token) do
|
||||
conn
|
||||
|> assign(:token, token_record)
|
||||
|> assign(:user, user)
|
||||
else
|
||||
_ ->
|
||||
# token found, but maybe only with app
|
||||
with {:ok, app, token_record} <- fetch_app_and_token(token) do
|
||||
conn
|
||||
|> assign(:token, token_record)
|
||||
|> assign(:app, app)
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
_ ->
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -44,6 +76,16 @@ defmodule Pleroma.Plugs.OAuthPlug do
|
|||
end
|
||||
end
|
||||
|
||||
@spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil
|
||||
defp fetch_app_and_token(token) do
|
||||
query =
|
||||
from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app])
|
||||
|
||||
with %Token{app: app} = token_record <- Repo.one(query) do
|
||||
{:ok, app, token_record}
|
||||
end
|
||||
end
|
||||
|
||||
# Gets token from session by :oauth_token key
|
||||
#
|
||||
@spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
|
||||
|
|
|
|||
36
lib/pleroma/plugs/rate_limit_plug.ex
Normal file
36
lib/pleroma/plugs/rate_limit_plug.ex
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.RateLimitPlug do
|
||||
import Phoenix.Controller, only: [json: 2]
|
||||
import Plug.Conn
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, opts) do
|
||||
enabled? = Pleroma.Config.get([:app_account_creation, :enabled])
|
||||
|
||||
case check_rate(conn, Map.put(opts, :enabled, enabled?)) do
|
||||
{:ok, _count} -> conn
|
||||
{:error, _count} -> render_error(conn)
|
||||
%Plug.Conn{} = conn -> conn
|
||||
end
|
||||
end
|
||||
|
||||
defp check_rate(conn, %{enabled: true} = opts) do
|
||||
max_requests = opts[:max_requests]
|
||||
bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
|
||||
|
||||
ExRated.check_rate(bucket_name, opts[:interval] * 1000, max_requests)
|
||||
end
|
||||
|
||||
defp check_rate(conn, _), do: conn
|
||||
|
||||
defp render_error(conn) do
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Rate limit exceeded."})
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
|
@ -19,4 +19,32 @@ defmodule Pleroma.Repo do
|
|||
def init(_, opts) do
|
||||
{:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))}
|
||||
end
|
||||
|
||||
@doc "find resource based on prepared query"
|
||||
@spec find_resource(Ecto.Query.t()) :: {:ok, struct()} | {:error, :not_found}
|
||||
def find_resource(%Ecto.Query{} = query) do
|
||||
case __MODULE__.one(query) do
|
||||
nil -> {:error, :not_found}
|
||||
resource -> {:ok, resource}
|
||||
end
|
||||
end
|
||||
|
||||
def find_resource(_query), do: {:error, :not_found}
|
||||
|
||||
@doc """
|
||||
Gets association from cache or loads if need
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Repo.get_assoc(token, :user)
|
||||
%User{}
|
||||
|
||||
"""
|
||||
@spec get_assoc(struct(), atom()) :: {:ok, struct()} | {:error, :not_found}
|
||||
def get_assoc(resource, association) do
|
||||
case __MODULE__.preload(resource, association) do
|
||||
%{^association => assoc} when not is_nil(assoc) -> {:ok, assoc}
|
||||
_ -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ defmodule Pleroma.Stats do
|
|||
def update_stats do
|
||||
peers =
|
||||
from(
|
||||
u in Pleroma.User,
|
||||
u in User,
|
||||
select: fragment("distinct split_part(?, '@', 2)", u.nickname),
|
||||
where: u.local != ^true
|
||||
)
|
||||
|
|
@ -44,10 +44,13 @@ defmodule Pleroma.Stats do
|
|||
domain_count = Enum.count(peers)
|
||||
|
||||
status_query =
|
||||
from(u in User.local_user_query(), select: fragment("sum((?->>'note_count')::int)", u.info))
|
||||
from(u in User.Query.build(%{local: true}),
|
||||
select: fragment("sum((?->>'note_count')::int)", u.info)
|
||||
)
|
||||
|
||||
status_count = Repo.one(status_query)
|
||||
user_count = Repo.aggregate(User.active_local_user_query(), :count, :id)
|
||||
|
||||
user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
|
||||
|
||||
Agent.update(__MODULE__, fn _ ->
|
||||
{peers, %{domain_count: domain_count, status_count: status_count, user_count: user_count}}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Upload do
|
||||
@moduledoc """
|
||||
# Upload
|
||||
Manage user uploads
|
||||
|
||||
Options:
|
||||
* `:type`: presets for activity type (defaults to Document) and size limits from app configuration
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
|||
|
||||
def process_response_body(body) do
|
||||
body
|
||||
|> Poison.decode!()
|
||||
|> Jason.decode!()
|
||||
end
|
||||
|
||||
def get_token do
|
||||
|
|
@ -38,7 +38,7 @@ defmodule Pleroma.Uploaders.Swift.Keystone do
|
|||
end
|
||||
|
||||
def make_auth_body(username, password, tenant) do
|
||||
Poison.encode!(%{
|
||||
Jason.encode!(%{
|
||||
:auth => %{
|
||||
:passwordCredentials => %{
|
||||
:username => username,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ defmodule Pleroma.User do
|
|||
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Registration
|
||||
|
|
@ -53,7 +52,6 @@ defmodule Pleroma.User do
|
|||
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_usec)
|
||||
has_many(:notifications, Notification)
|
||||
has_many(:registrations, Registration)
|
||||
|
|
@ -206,14 +204,15 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
confirmation_status =
|
||||
if opts[:confirmed] || !Pleroma.Config.get([:instance, :account_activation_required]) do
|
||||
:confirmed
|
||||
need_confirmation? =
|
||||
if is_nil(opts[:need_confirmation]) do
|
||||
Pleroma.Config.get([:instance, :account_activation_required])
|
||||
else
|
||||
:unconfirmed
|
||||
opts[:need_confirmation]
|
||||
end
|
||||
|
||||
info_change = User.Info.confirmation_changeset(%User.Info{}, confirmation_status)
|
||||
info_change =
|
||||
User.Info.confirmation_changeset(%User.Info{}, need_confirmation: need_confirmation?)
|
||||
|
||||
changeset =
|
||||
struct
|
||||
|
|
@ -256,10 +255,7 @@ defmodule Pleroma.User do
|
|||
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
|
||||
|
||||
autofollowed_users =
|
||||
from(u in User,
|
||||
where: u.local == true,
|
||||
where: u.nickname in ^candidates
|
||||
)
|
||||
User.Query.build(%{nickname: candidates, local: true})
|
||||
|> Repo.all()
|
||||
|
||||
follow_all(user, autofollowed_users)
|
||||
|
|
@ -422,7 +418,7 @@ defmodule Pleroma.User do
|
|||
Enum.map(
|
||||
followed_identifiers,
|
||||
fn followed_identifier ->
|
||||
with %User{} = followed <- get_or_fetch(followed_identifier),
|
||||
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
|
||||
{:ok, follower} <- maybe_direct_follow(follower, followed),
|
||||
{:ok, _} <- ActivityPub.follow(follower, followed) do
|
||||
followed
|
||||
|
|
@ -506,7 +502,15 @@ defmodule Pleroma.User do
|
|||
|
||||
def get_cached_by_nickname(nickname) do
|
||||
key = "nickname:#{nickname}"
|
||||
Cachex.fetch!(:user_cache, key, fn _ -> get_or_fetch_by_nickname(nickname) end)
|
||||
|
||||
Cachex.fetch!(:user_cache, key, fn ->
|
||||
user_result = get_or_fetch_by_nickname(nickname)
|
||||
|
||||
case user_result do
|
||||
{:ok, user} -> {:commit, user}
|
||||
{:error, _error} -> {:ignore, nil}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def get_cached_by_nickname_or_id(nickname_or_id) do
|
||||
|
|
@ -542,7 +546,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def get_or_fetch_by_nickname(nickname) do
|
||||
with %User{} = user <- get_by_nickname(nickname) do
|
||||
user
|
||||
{:ok, user}
|
||||
else
|
||||
_e ->
|
||||
with [_nick, _domain] <- String.split(nickname, "@"),
|
||||
|
|
@ -552,9 +556,9 @@ defmodule Pleroma.User do
|
|||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||
end
|
||||
|
||||
user
|
||||
{:ok, user}
|
||||
else
|
||||
_e -> nil
|
||||
_e -> {:error, "not found " <> nickname}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -570,19 +574,17 @@ defmodule Pleroma.User do
|
|||
)
|
||||
end
|
||||
|
||||
def get_followers_query(%User{id: id, follower_address: follower_address}, nil) do
|
||||
from(
|
||||
u in User,
|
||||
where: fragment("? <@ ?", ^[follower_address], u.following),
|
||||
where: u.id != ^id
|
||||
)
|
||||
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||
def get_followers_query(%User{} = user, nil) do
|
||||
User.Query.build(%{followers: user})
|
||||
end
|
||||
|
||||
def get_followers_query(user, page) do
|
||||
from(u in get_followers_query(user, nil))
|
||||
|> paginate(page, 20)
|
||||
|> User.Query.paginate(page, 20)
|
||||
end
|
||||
|
||||
@spec get_followers_query(User.t()) :: Ecto.Query.t()
|
||||
def get_followers_query(user), do: get_followers_query(user, nil)
|
||||
|
||||
def get_followers(user, page \\ nil) do
|
||||
|
|
@ -597,19 +599,17 @@ defmodule Pleroma.User do
|
|||
Repo.all(from(u in q, select: u.id))
|
||||
end
|
||||
|
||||
def get_friends_query(%User{id: id, following: following}, nil) do
|
||||
from(
|
||||
u in User,
|
||||
where: u.follower_address in ^following,
|
||||
where: u.id != ^id
|
||||
)
|
||||
@spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
|
||||
def get_friends_query(%User{} = user, nil) do
|
||||
User.Query.build(%{friends: user})
|
||||
end
|
||||
|
||||
def get_friends_query(user, page) do
|
||||
from(u in get_friends_query(user, nil))
|
||||
|> paginate(page, 20)
|
||||
|> User.Query.paginate(page, 20)
|
||||
end
|
||||
|
||||
@spec get_friends_query(User.t()) :: Ecto.Query.t()
|
||||
def get_friends_query(user), do: get_friends_query(user, nil)
|
||||
|
||||
def get_friends(user, page \\ nil) do
|
||||
|
|
@ -624,33 +624,10 @@ defmodule Pleroma.User do
|
|||
Repo.all(from(u in q, select: u.id))
|
||||
end
|
||||
|
||||
def get_follow_requests_query(%User{} = user) do
|
||||
from(
|
||||
a in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"? ->> 'type' = 'Follow'",
|
||||
a.data
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"? ->> 'state' = 'pending'",
|
||||
a.data
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"coalesce((?)->'object'->>'id', (?)->>'object') = ?",
|
||||
a.data,
|
||||
a.data,
|
||||
^user.ap_id
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
@spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
|
||||
def get_follow_requests(%User{} = user) do
|
||||
users =
|
||||
user
|
||||
|> User.get_follow_requests_query()
|
||||
Activity.follow_requests_for_actor(user)
|
||||
|> 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)
|
||||
|
|
@ -723,10 +700,7 @@ defmodule Pleroma.User do
|
|||
|
||||
def update_follower_count(%User{} = user) do
|
||||
follower_count_query =
|
||||
User
|
||||
|> where([u], ^user.follower_address in u.following)
|
||||
|> where([u], u.id != ^user.id)
|
||||
|> select([u], %{count: count(u.id)})
|
||||
User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})
|
||||
|
||||
User
|
||||
|> where(id: ^user.id)
|
||||
|
|
@ -749,38 +723,19 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def get_users_from_set_query(ap_ids, false) do
|
||||
from(
|
||||
u in User,
|
||||
where: u.ap_id in ^ap_ids
|
||||
)
|
||||
end
|
||||
|
||||
def get_users_from_set_query(ap_ids, true) do
|
||||
query = get_users_from_set_query(ap_ids, false)
|
||||
|
||||
from(
|
||||
u in query,
|
||||
where: u.local == true
|
||||
)
|
||||
end
|
||||
|
||||
@spec get_users_from_set([String.t()], boolean()) :: [User.t()]
|
||||
def get_users_from_set(ap_ids, local_only \\ true) do
|
||||
get_users_from_set_query(ap_ids, local_only)
|
||||
criteria = %{ap_id: ap_ids}
|
||||
criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
|
||||
|
||||
User.Query.build(criteria)
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec get_recipients_from_activity(Activity.t()) :: [User.t()]
|
||||
def get_recipients_from_activity(%Activity{recipients: to}) do
|
||||
query =
|
||||
from(
|
||||
u in User,
|
||||
where: u.ap_id in ^to,
|
||||
or_where: fragment("? && ?", u.following, ^to)
|
||||
)
|
||||
|
||||
query = from(u in query, where: u.local == true)
|
||||
|
||||
Repo.all(query)
|
||||
User.Query.build(%{recipients_from_activity: to, local: true})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def search(query, resolve \\ false, for_user \\ nil) do
|
||||
|
|
@ -901,7 +856,7 @@ defmodule Pleroma.User do
|
|||
Enum.map(
|
||||
blocked_identifiers,
|
||||
fn blocked_identifier ->
|
||||
with %User{} = blocked <- get_or_fetch(blocked_identifier),
|
||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||
{:ok, blocker} <- block(blocker, blocked),
|
||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
||||
blocked
|
||||
|
|
@ -1042,14 +997,23 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def muted_users(user),
|
||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.mutes))
|
||||
@spec muted_users(User.t()) :: [User.t()]
|
||||
def muted_users(user) do
|
||||
User.Query.build(%{ap_id: user.info.mutes})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def blocked_users(user),
|
||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
|
||||
@spec blocked_users(User.t()) :: [User.t()]
|
||||
def blocked_users(user) do
|
||||
User.Query.build(%{ap_id: user.info.blocks})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def subscribers(user),
|
||||
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.subscribers))
|
||||
@spec subscribers(User.t()) :: [User.t()]
|
||||
def subscribers(user) do
|
||||
User.Query.build(%{ap_id: user.info.subscribers})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
def block_domain(user, domain) do
|
||||
info_cng =
|
||||
|
|
@ -1075,69 +1039,6 @@ defmodule Pleroma.User do
|
|||
update_and_set_cache(cng)
|
||||
end
|
||||
|
||||
def maybe_local_user_query(query, local) do
|
||||
if local, do: local_user_query(query), else: query
|
||||
end
|
||||
|
||||
def local_user_query(query \\ User) do
|
||||
from(
|
||||
u in query,
|
||||
where: u.local == true,
|
||||
where: not is_nil(u.nickname)
|
||||
)
|
||||
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(),
|
||||
where: fragment("not (?->'deactivated' @> 'true')", u.info)
|
||||
)
|
||||
end
|
||||
|
||||
def moderator_user_query do
|
||||
from(
|
||||
u in User,
|
||||
where: u.local == true,
|
||||
where: fragment("?->'is_moderator' @> 'true'", u.info)
|
||||
)
|
||||
end
|
||||
|
||||
def deactivate(%User{} = user, status \\ true) do
|
||||
info_cng = User.Info.set_activation_status(user.info, status)
|
||||
|
||||
|
|
@ -1156,7 +1057,12 @@ defmodule Pleroma.User do
|
|||
|> update_and_set_cache()
|
||||
end
|
||||
|
||||
def delete(%User{} = user) do
|
||||
@spec delete(User.t()) :: :ok
|
||||
def delete(%User{} = user),
|
||||
do: PleromaJobQueue.enqueue(:background, __MODULE__, [:delete, user])
|
||||
|
||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||
def perform(:delete, %User{} = user) do
|
||||
{:ok, user} = User.deactivate(user)
|
||||
|
||||
# Remove all relationships
|
||||
|
|
@ -1172,22 +1078,23 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
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()
|
||||
stream =
|
||||
ap_id
|
||||
|> Activity.query_by_actor()
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Repo.stream()
|
||||
|
||||
# TODO: Do something with likes, follows, repeats.
|
||||
_ ->
|
||||
"Doing nothing"
|
||||
end)
|
||||
Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
|
||||
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
|
||||
Object.normalize(activity) |> ActivityPub.delete()
|
||||
end
|
||||
|
||||
defp delete_activity(_activity), do: "Doing nothing"
|
||||
|
||||
def html_filter_policy(%User{info: %{no_rich_text: true}}) do
|
||||
Pleroma.HTML.Scrubber.TwitterText
|
||||
end
|
||||
|
|
@ -1201,11 +1108,11 @@ defmodule Pleroma.User do
|
|||
|
||||
case ap_try do
|
||||
{:ok, user} ->
|
||||
user
|
||||
{:ok, user}
|
||||
|
||||
_ ->
|
||||
case OStatus.make_user(ap_id) do
|
||||
{:ok, user} -> user
|
||||
{:ok, user} -> {:ok, user}
|
||||
_ -> {:error, "Could not fetch by AP id"}
|
||||
end
|
||||
end
|
||||
|
|
@ -1215,20 +1122,20 @@ defmodule Pleroma.User do
|
|||
user = get_cached_by_ap_id(ap_id)
|
||||
|
||||
if !is_nil(user) and !User.needs_update?(user) do
|
||||
user
|
||||
{:ok, user}
|
||||
else
|
||||
# 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])
|
||||
|
||||
user = fetch_by_ap_id(ap_id)
|
||||
resp = fetch_by_ap_id(ap_id)
|
||||
|
||||
if should_fetch_initial do
|
||||
with %User{} = user do
|
||||
with {:ok, %User{} = user} = resp do
|
||||
{:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
resp
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1270,7 +1177,7 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def get_public_key_for_ap_id(ap_id) do
|
||||
with %User{} = user <- get_or_fetch_by_ap_id(ap_id),
|
||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||
{:ok, public_key} <- public_key_from_info(user.info) do
|
||||
{:ok, public_key}
|
||||
else
|
||||
|
|
@ -1294,7 +1201,7 @@ defmodule Pleroma.User do
|
|||
def ap_enabled?(_), do: false
|
||||
|
||||
@doc "Gets or fetch a user by uri or nickname."
|
||||
@spec get_or_fetch(String.t()) :: User.t()
|
||||
@spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
|
||||
def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
|
||||
def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname)
|
||||
|
||||
|
|
@ -1322,18 +1229,15 @@ defmodule Pleroma.User do
|
|||
end
|
||||
end
|
||||
|
||||
def parse_bio(bio, user \\ %User{info: %{source_data: %{}}})
|
||||
def parse_bio(nil, _user), do: ""
|
||||
def parse_bio(bio, _user) when bio == "", do: bio
|
||||
def parse_bio(bio) when is_binary(bio) and bio != "" do
|
||||
bio
|
||||
|> CommonUtils.format_input("text/plain", mentions_format: :full)
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
def parse_bio(bio, user) do
|
||||
emoji =
|
||||
(user.info.source_data["tag"] || [])
|
||||
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|
||||
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
|
||||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
def parse_bio(_), do: ""
|
||||
|
||||
def parse_bio(bio, user) when is_binary(bio) and bio != "" do
|
||||
# TODO: get profile URLs other than user.ap_id
|
||||
profile_urls = [user.ap_id]
|
||||
|
||||
|
|
@ -1343,9 +1247,10 @@ defmodule Pleroma.User do
|
|||
rel: &RelMe.maybe_put_rel_me(&1, profile_urls)
|
||||
)
|
||||
|> elem(0)
|
||||
|> Formatter.emojify(emoji)
|
||||
end
|
||||
|
||||
def parse_bio(_, _), do: ""
|
||||
|
||||
def tag(user_identifiers, tags) when is_list(user_identifiers) do
|
||||
Repo.transaction(fn ->
|
||||
for user_identifier <- user_identifiers, do: tag(user_identifier, tags)
|
||||
|
|
@ -1379,22 +1284,6 @@ defmodule Pleroma.User do
|
|||
updated_user
|
||||
end
|
||||
|
||||
def bookmark(%User{} = user, status_id) do
|
||||
bookmarks = Enum.uniq(user.bookmarks ++ [status_id])
|
||||
update_bookmarks(user, bookmarks)
|
||||
end
|
||||
|
||||
def unbookmark(%User{} = user, status_id) do
|
||||
bookmarks = Enum.uniq(user.bookmarks -- [status_id])
|
||||
update_bookmarks(user, bookmarks)
|
||||
end
|
||||
|
||||
def update_bookmarks(%User{} = user, bookmarks) do
|
||||
user
|
||||
|> change(%{bookmarks: bookmarks})
|
||||
|> update_and_set_cache
|
||||
end
|
||||
|
||||
defp normalize_tags(tags) do
|
||||
[tags]
|
||||
|> List.flatten()
|
||||
|
|
@ -1429,22 +1318,12 @@ defmodule Pleroma.User do
|
|||
}
|
||||
end
|
||||
|
||||
@spec all_superusers() :: [User.t()]
|
||||
def all_superusers do
|
||||
from(
|
||||
u in User,
|
||||
where: u.local == true,
|
||||
where: fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
|
||||
)
|
||||
User.Query.build(%{super_users: true, local: true})
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
defp paginate(query, page, page_size) do
|
||||
from(u in query,
|
||||
limit: ^page_size,
|
||||
offset: ^((page - 1) * page_size)
|
||||
)
|
||||
end
|
||||
|
||||
def showing_reblogs?(%User{} = user, %User{} = target) do
|
||||
target.ap_id not in user.info.muted_reblogs
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ defmodule Pleroma.User.Info do
|
|||
|
||||
alias Pleroma.User.Info
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
embedded_schema do
|
||||
field(:banner, :map, default: %{})
|
||||
field(:background, :map, default: %{})
|
||||
|
|
@ -41,6 +43,7 @@ defmodule Pleroma.User.Info do
|
|||
field(:hide_favorites, :boolean, default: true)
|
||||
field(:pinned_activities, {:array, :string}, default: [])
|
||||
field(:flavour, :string, default: nil)
|
||||
field(:emoji, {:array, :map}, default: [])
|
||||
|
||||
field(:notification_settings, :map,
|
||||
default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
|
||||
|
|
@ -209,32 +212,26 @@ defmodule Pleroma.User.Info do
|
|||
])
|
||||
end
|
||||
|
||||
def confirmation_changeset(info, :confirmed) do
|
||||
confirmation_changeset(info, %{
|
||||
confirmation_pending: false,
|
||||
confirmation_token: nil
|
||||
})
|
||||
end
|
||||
@spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t()
|
||||
def confirmation_changeset(info, opts) do
|
||||
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
||||
|
||||
def confirmation_changeset(info, :unconfirmed) do
|
||||
confirmation_changeset(info, %{
|
||||
confirmation_pending: true,
|
||||
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
})
|
||||
end
|
||||
params =
|
||||
if need_confirmation? do
|
||||
%{
|
||||
confirmation_pending: true,
|
||||
confirmation_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64()
|
||||
}
|
||||
else
|
||||
%{
|
||||
confirmation_pending: false,
|
||||
confirmation_token: nil
|
||||
}
|
||||
end
|
||||
|
||||
def confirmation_changeset(info, params) do
|
||||
cast(info, params, [:confirmation_pending, :confirmation_token])
|
||||
end
|
||||
|
||||
def mastodon_profile_update(info, params) do
|
||||
info
|
||||
|> cast(params, [
|
||||
:locked,
|
||||
:banner
|
||||
])
|
||||
end
|
||||
|
||||
def mastodon_settings_update(info, settings) do
|
||||
params = %{settings: settings}
|
||||
|
||||
|
|
|
|||
150
lib/pleroma/user/query.ex
Normal file
150
lib/pleroma/user/query.ex
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.User.Query do
|
||||
@moduledoc """
|
||||
User query builder module. Builds query from new query or another user query.
|
||||
|
||||
## Example:
|
||||
query = Pleroma.User.Query(%{nickname: "nickname"})
|
||||
another_query = Pleroma.User.Query.build(query, %{email: "email@example.com"})
|
||||
Pleroma.Repo.all(query)
|
||||
Pleroma.Repo.all(another_query)
|
||||
|
||||
Adding new rules:
|
||||
- *ilike criteria*
|
||||
- add field to @ilike_criteria list
|
||||
- pass non empty string
|
||||
- e.g. Pleroma.User.Query.build(%{nickname: "nickname"})
|
||||
- *equal criteria*
|
||||
- add field to @equal_criteria list
|
||||
- pass non empty string
|
||||
- e.g. Pleroma.User.Query.build(%{email: "email@example.com"})
|
||||
- *contains criteria*
|
||||
- add field to @containns_criteria list
|
||||
- pass values list
|
||||
- e.g. Pleroma.User.Query.build(%{ap_id: ["http://ap_id1", "http://ap_id2"]})
|
||||
"""
|
||||
import Ecto.Query
|
||||
import Pleroma.Web.AdminAPI.Search, only: [not_empty_string: 1]
|
||||
alias Pleroma.User
|
||||
|
||||
@type criteria ::
|
||||
%{
|
||||
query: String.t(),
|
||||
tags: [String.t()],
|
||||
name: String.t(),
|
||||
email: String.t(),
|
||||
local: boolean(),
|
||||
external: boolean(),
|
||||
active: boolean(),
|
||||
deactivated: boolean(),
|
||||
is_admin: boolean(),
|
||||
is_moderator: boolean(),
|
||||
super_users: boolean(),
|
||||
followers: User.t(),
|
||||
friends: User.t(),
|
||||
recipients_from_activity: [String.t()],
|
||||
nickname: [String.t()],
|
||||
ap_id: [String.t()]
|
||||
}
|
||||
| %{}
|
||||
|
||||
@ilike_criteria [:nickname, :name, :query]
|
||||
@equal_criteria [:email]
|
||||
@role_criteria [:is_admin, :is_moderator]
|
||||
@contains_criteria [:ap_id, :nickname]
|
||||
|
||||
@spec build(criteria()) :: Query.t()
|
||||
def build(query \\ base_query(), criteria) do
|
||||
prepare_query(query, criteria)
|
||||
end
|
||||
|
||||
@spec paginate(Ecto.Query.t(), pos_integer(), pos_integer()) :: Ecto.Query.t()
|
||||
def paginate(query, page, page_size) do
|
||||
from(u in query,
|
||||
limit: ^page_size,
|
||||
offset: ^((page - 1) * page_size)
|
||||
)
|
||||
end
|
||||
|
||||
defp base_query do
|
||||
from(u in User)
|
||||
end
|
||||
|
||||
defp prepare_query(query, criteria) do
|
||||
Enum.reduce(criteria, query, &compose_query/2)
|
||||
end
|
||||
|
||||
defp compose_query({key, value}, query)
|
||||
when key in @ilike_criteria and not_empty_string(value) do
|
||||
# hack for :query key
|
||||
key = if key == :query, do: :nickname, else: key
|
||||
where(query, [u], ilike(field(u, ^key), ^"%#{value}%"))
|
||||
end
|
||||
|
||||
defp compose_query({key, value}, query)
|
||||
when key in @equal_criteria and not_empty_string(value) do
|
||||
where(query, [u], ^[{key, value}])
|
||||
end
|
||||
|
||||
defp compose_query({key, values}, query) when key in @contains_criteria and is_list(values) do
|
||||
where(query, [u], field(u, ^key) in ^values)
|
||||
end
|
||||
|
||||
defp compose_query({:tags, tags}, query) when is_list(tags) and length(tags) > 0 do
|
||||
Enum.reduce(tags, query, &prepare_tag_criteria/2)
|
||||
end
|
||||
|
||||
defp compose_query({key, _}, query) when key in @role_criteria do
|
||||
where(query, [u], fragment("(?->? @> 'true')", u.info, ^to_string(key)))
|
||||
end
|
||||
|
||||
defp compose_query({:super_users, _}, query) do
|
||||
where(
|
||||
query,
|
||||
[u],
|
||||
fragment("?->'is_admin' @> 'true' OR ?->'is_moderator' @> 'true'", u.info, u.info)
|
||||
)
|
||||
end
|
||||
|
||||
defp compose_query({:local, _}, query), do: location_query(query, true)
|
||||
|
||||
defp compose_query({:external, _}, query), do: location_query(query, false)
|
||||
|
||||
defp compose_query({:active, _}, query) do
|
||||
where(query, [u], fragment("not (?->'deactivated' @> 'true')", u.info))
|
||||
|> where([u], not is_nil(u.nickname))
|
||||
end
|
||||
|
||||
defp compose_query({:deactivated, _}, query) do
|
||||
where(query, [u], fragment("?->'deactivated' @> 'true'", u.info))
|
||||
|> where([u], not is_nil(u.nickname))
|
||||
end
|
||||
|
||||
defp compose_query({:followers, %User{id: id, follower_address: follower_address}}, query) do
|
||||
where(query, [u], fragment("? <@ ?", ^[follower_address], u.following))
|
||||
|> where([u], u.id != ^id)
|
||||
end
|
||||
|
||||
defp compose_query({:friends, %User{id: id, following: following}}, query) do
|
||||
where(query, [u], u.follower_address in ^following)
|
||||
|> where([u], u.id != ^id)
|
||||
end
|
||||
|
||||
defp compose_query({:recipients_from_activity, to}, query) do
|
||||
where(query, [u], u.ap_id in ^to or fragment("? && ?", u.following, ^to))
|
||||
end
|
||||
|
||||
defp compose_query(_unsupported_param, query), do: query
|
||||
|
||||
defp prepare_tag_criteria(tag, query) do
|
||||
or_where(query, [u], fragment("? = any(?)", ^tag, u.tags))
|
||||
end
|
||||
|
||||
defp location_query(query, local) do
|
||||
where(query, [u], u.local == ^local)
|
||||
|> where([u], not is_nil(u.nickname))
|
||||
end
|
||||
end
|
||||
|
|
@ -24,7 +24,7 @@ defmodule Pleroma.UserInviteToken do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
@spec create_invite(map()) :: UserInviteToken.t()
|
||||
@spec create_invite(map()) :: {:ok, UserInviteToken.t()}
|
||||
def create_invite(params \\ %{}) do
|
||||
%UserInviteToken{}
|
||||
|> cast(params, [:max_use, :expires_at])
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
|
@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
import Ecto.Query
|
||||
|
|
@ -23,8 +22,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|
||||
require Logger
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
# For Announce activities, we filter the recipients based on following status for any actors
|
||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
||||
defp get_recipients(%{"type" => "Announce"} = data) do
|
||||
|
|
@ -140,7 +137,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end)
|
||||
|
||||
Notification.create_notifications(activity)
|
||||
|
||||
participations =
|
||||
activity
|
||||
|> Conversation.create_or_bump_for()
|
||||
|> get_participations()
|
||||
|
||||
stream_out(activity)
|
||||
stream_out_participations(participations)
|
||||
{:ok, activity}
|
||||
else
|
||||
%Activity{} = activity ->
|
||||
|
|
@ -163,11 +167,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_participations({:ok, %{participations: participations}}), do: participations
|
||||
defp get_participations(_), do: []
|
||||
|
||||
def stream_out_participations(participations) do
|
||||
participations =
|
||||
participations
|
||||
|> Repo.preload(:user)
|
||||
|
||||
Enum.each(participations, fn participation ->
|
||||
Pleroma.Web.Streamer.stream("participation", participation)
|
||||
end)
|
||||
end
|
||||
|
||||
def stream_out(activity) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
if activity.data["type"] in ["Create", "Announce", "Delete"] do
|
||||
object = Object.normalize(activity)
|
||||
Pleroma.Web.Streamer.stream("user", activity)
|
||||
Pleroma.Web.Streamer.stream("list", activity)
|
||||
|
||||
|
|
@ -179,6 +195,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
if activity.data["type"] in ["Create"] do
|
||||
object = Object.normalize(activity)
|
||||
|
||||
object.data
|
||||
|> Map.get("tag", [])
|
||||
|> Enum.filter(fn tag -> is_bitstring(tag) end)
|
||||
|
|
@ -193,6 +211,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
else
|
||||
# TODO: Write test, replace with visibility test
|
||||
if !Enum.member?(activity.data["cc"] || [], public) &&
|
||||
!Enum.member?(
|
||||
activity.data["to"],
|
||||
|
|
@ -455,35 +474,44 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
defp fetch_activities_for_context_query(context, opts) do
|
||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
|
||||
recipients =
|
||||
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
||||
|
||||
query = from(activity in Activity)
|
||||
|
||||
query =
|
||||
query
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|
||||
query =
|
||||
from(
|
||||
activity in query,
|
||||
where:
|
||||
fragment(
|
||||
"?->>'type' = ? and ?->>'context' = ?",
|
||||
activity.data,
|
||||
"Create",
|
||||
activity.data,
|
||||
^context
|
||||
),
|
||||
order_by: [desc: :id]
|
||||
from(activity in Activity)
|
||||
|> restrict_blocked(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|> where(
|
||||
[activity],
|
||||
fragment(
|
||||
"?->>'type' = ? and ?->>'context' = ?",
|
||||
activity.data,
|
||||
"Create",
|
||||
activity.data,
|
||||
^context
|
||||
)
|
||||
|> Activity.with_preloaded_object()
|
||||
)
|
||||
|> order_by([activity], desc: activity.id)
|
||||
end
|
||||
|
||||
Repo.all(query)
|
||||
@spec fetch_activities_for_context(String.t(), keyword() | map()) :: [Activity.t()]
|
||||
def fetch_activities_for_context(context, opts \\ %{}) do
|
||||
context
|
||||
|> fetch_activities_for_context_query(opts)
|
||||
|> Activity.with_preloaded_object()
|
||||
|> Repo.all()
|
||||
end
|
||||
|
||||
@spec fetch_latest_activity_id_for_context(String.t(), keyword() | map()) ::
|
||||
Pleroma.FlakeId.t() | nil
|
||||
def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
|
||||
context
|
||||
|> fetch_activities_for_context_query(opts)
|
||||
|> limit(1)
|
||||
|> select([a], a.id)
|
||||
|> Repo.one()
|
||||
end
|
||||
|
||||
def fetch_public_activities(opts \\ %{}) do
|
||||
|
|
@ -782,9 +810,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
|> Activity.with_preloaded_object()
|
||||
end
|
||||
|
||||
defp maybe_preload_bookmarks(query, %{"skip_preload" => true}), do: query
|
||||
|
||||
defp maybe_preload_bookmarks(query, opts) do
|
||||
query
|
||||
|> Activity.with_preloaded_bookmark(opts["user"])
|
||||
end
|
||||
|
||||
defp maybe_order(query, %{order: :desc}) do
|
||||
query
|
||||
|> order_by(desc: :id)
|
||||
end
|
||||
|
||||
defp maybe_order(query, %{order: :asc}) do
|
||||
query
|
||||
|> order_by(asc: :id)
|
||||
end
|
||||
|
||||
defp maybe_order(query, _), do: query
|
||||
|
||||
def fetch_activities_query(recipients, opts \\ %{}) do
|
||||
Activity
|
||||
|> maybe_preload_objects(opts)
|
||||
|> maybe_preload_bookmarks(opts)
|
||||
|> maybe_order(opts)
|
||||
|> restrict_recipients(recipients, opts["user"])
|
||||
|> restrict_tag(opts)
|
||||
|> restrict_tag_reject(opts)
|
||||
|
|
@ -925,134 +974,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def should_federate?(inbox, public) do
|
||||
if public do
|
||||
true
|
||||
else
|
||||
inbox_info = URI.parse(inbox)
|
||||
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||
end
|
||||
end
|
||||
|
||||
defp recipients(actor, activity) do
|
||||
followers =
|
||||
if actor.follower_address in activity.recipients do
|
||||
{:ok, followers} = User.get_followers(actor)
|
||||
Enum.filter(followers, &(!&1.local))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
|
||||
end
|
||||
|
||||
defp get_cc_ap_ids(ap_id, recipients) do
|
||||
host = Map.get(URI.parse(ap_id), :host)
|
||||
|
||||
recipients
|
||||
|> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
|
||||
|> Enum.map(& &1.ap_id)
|
||||
end
|
||||
|
||||
def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
|
||||
public = is_public?(activity)
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
recipients = recipients(actor, activity)
|
||||
|
||||
recipients
|
||||
|> Enum.filter(&User.ap_enabled?/1)
|
||||
|> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|> Instances.filter_reachable()
|
||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||
%User{ap_id: ap_id} =
|
||||
Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
|
||||
|
||||
cc = get_cc_ap_ids(ap_id, recipients)
|
||||
|
||||
json =
|
||||
data
|
||||
|> Map.put("cc", cc)
|
||||
|> Map.put("directMessage", true)
|
||||
|> Jason.encode!()
|
||||
|
||||
Federator.publish_single_ap(%{
|
||||
inbox: inbox,
|
||||
json: json,
|
||||
actor: actor,
|
||||
id: activity.data["id"],
|
||||
unreachable_since: unreachable_since
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
def publish(actor, activity) do
|
||||
public = is_public?(activity)
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
json = Jason.encode!(data)
|
||||
|
||||
recipients(actor, activity)
|
||||
|> Enum.filter(&User.ap_enabled?/1)
|
||||
|> Enum.map(fn %{info: %{source_data: data}} ->
|
||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|> Instances.filter_reachable()
|
||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||
Federator.publish_single_ap(%{
|
||||
inbox: inbox,
|
||||
json: json,
|
||||
actor: actor,
|
||||
id: activity.data["id"],
|
||||
unreachable_since: unreachable_since
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
def publish_one(%{inbox: inbox, json: json, actor: actor, id: id} = params) do
|
||||
Logger.info("Federating #{id} to #{inbox}")
|
||||
host = URI.parse(inbox).host
|
||||
|
||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||
|
||||
date =
|
||||
NaiveDateTime.utc_now()
|
||||
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||
|
||||
signature =
|
||||
Pleroma.Web.HTTPSignatures.sign(actor, %{
|
||||
host: host,
|
||||
"content-length": byte_size(json),
|
||||
digest: digest,
|
||||
date: date
|
||||
})
|
||||
|
||||
with {:ok, %{status: code}} when code in 200..299 <-
|
||||
result =
|
||||
@httpoison.post(
|
||||
inbox,
|
||||
json,
|
||||
[
|
||||
{"Content-Type", "application/activity+json"},
|
||||
{"Date", date},
|
||||
{"signature", signature},
|
||||
{"digest", digest}
|
||||
]
|
||||
) do
|
||||
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
||||
do: Instances.set_reachable(inbox)
|
||||
|
||||
result
|
||||
else
|
||||
{_post_result, response} ->
|
||||
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
|
||||
{:error, response}
|
||||
end
|
||||
end
|
||||
|
||||
# filter out broken threads
|
||||
def contain_broken_threads(%Activity{} = activity, %User{} = user) do
|
||||
entire_thread_visible_for_user?(activity, user)
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
|||
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
||||
with %User{} = recipient <- User.get_cached_by_nickname(nickname),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(params["actor"]),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
|
||||
true <- Utils.recipient_in_message(recipient, actor, params),
|
||||
params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
|
||||
Federator.incoming_ap_doc(params)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do
|
||||
alias Pleroma.User
|
||||
|
||||
@moduledoc "Prevent followbots from following with a bit of heuristic"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
# XXX: this should become User.normalize_by_ap_id() or similar, really.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
|
||||
require Logger
|
||||
@moduledoc "Drop and log everything received"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
|
||||
alias Pleroma.Object
|
||||
|
||||
@moduledoc "Ensure a re: is prepended on replies to a post with a Subject"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
||||
alias Pleroma.User
|
||||
@moduledoc "Block messages with too much mentions (configurable)"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
defp delist_message(message, threshold) when threshold > 0 do
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
defp string_matches?(string, _) when not is_binary(string) do
|
||||
false
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do
|
||||
@moduledoc "Ensure no content placeholder is present (such as the dot from mastodon)"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
|
||||
@moduledoc "Does nothing (lets the messages go through unmodified)"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
||||
@moduledoc "Scrub configured hypertext markup"
|
||||
alias Pleroma.HTML
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||
alias Pleroma.User
|
||||
@moduledoc "Rejects non-public (followers-only, direct) activities"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||
alias Pleroma.User
|
||||
@moduledoc "Filter activities depending on their origin instance"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||
|
|
|
|||
|
|
@ -5,6 +5,19 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
||||
alias Pleroma.User
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
@moduledoc """
|
||||
Apply policies based on user tags
|
||||
|
||||
This policy applies policies on a user activities depending on their tags
|
||||
on your instance.
|
||||
|
||||
- `mrf_tag:media-force-nsfw`: Mark as sensitive on presence of attachments
|
||||
- `mrf_tag:media-strip`: Remove attachments
|
||||
- `mrf_tag:force-unlisted`: Mark as unlisted (removes from the federated timeline)
|
||||
- `mrf_tag:sandbox`: Remove from public (local and federated) timelines
|
||||
- `mrf_tag:disable-remote-subscription`: Reject non-local follow requests
|
||||
- `mrf_tag:disable-any-subscription`: Reject any follow requests
|
||||
"""
|
||||
|
||||
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
|
||||
defp get_tags(_), do: []
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
|
||||
alias Pleroma.Config
|
||||
|
||||
@moduledoc "Accept-list of users from specified instances"
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
defp filter_by_list(object, []), do: {:ok, object}
|
||||
|
|
|
|||
201
lib/pleroma/web/activity_pub/publisher.ex
Normal file
201
lib/pleroma/web/activity_pub/publisher.ex
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
import Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
@behaviour Pleroma.Web.Federator.Publisher
|
||||
|
||||
require Logger
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
@moduledoc """
|
||||
ActivityPub outgoing federation module.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Determine if an activity can be represented by running it through Transmogrifier.
|
||||
"""
|
||||
def is_representable?(%Activity{} = activity) do
|
||||
with {:ok, _data} <- Transmogrifier.prepare_outgoing(activity.data) do
|
||||
true
|
||||
else
|
||||
_e ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Publish a single message to a peer. Takes a struct with the following
|
||||
parameters set:
|
||||
|
||||
* `inbox`: the inbox to publish to
|
||||
* `json`: the JSON message body representing the ActivityPub message
|
||||
* `actor`: the actor which is signing the message
|
||||
* `id`: the ActivityStreams URI of the message
|
||||
"""
|
||||
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
|
||||
Logger.info("Federating #{id} to #{inbox}")
|
||||
host = URI.parse(inbox).host
|
||||
|
||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
|
||||
|
||||
date =
|
||||
NaiveDateTime.utc_now()
|
||||
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
|
||||
|
||||
signature =
|
||||
Pleroma.Web.HTTPSignatures.sign(actor, %{
|
||||
host: host,
|
||||
"content-length": byte_size(json),
|
||||
digest: digest,
|
||||
date: date
|
||||
})
|
||||
|
||||
with {:ok, %{status: code}} when code in 200..299 <-
|
||||
result =
|
||||
@httpoison.post(
|
||||
inbox,
|
||||
json,
|
||||
[
|
||||
{"Content-Type", "application/activity+json"},
|
||||
{"Date", date},
|
||||
{"signature", signature},
|
||||
{"digest", digest}
|
||||
]
|
||||
) do
|
||||
if !Map.has_key?(params, :unreachable_since) || params[:unreachable_since],
|
||||
do: Instances.set_reachable(inbox)
|
||||
|
||||
result
|
||||
else
|
||||
{_post_result, response} ->
|
||||
unless params[:unreachable_since], do: Instances.set_unreachable(inbox)
|
||||
{:error, response}
|
||||
end
|
||||
end
|
||||
|
||||
defp should_federate?(inbox, public) do
|
||||
if public do
|
||||
true
|
||||
else
|
||||
inbox_info = URI.parse(inbox)
|
||||
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||
end
|
||||
end
|
||||
|
||||
defp recipients(actor, activity) do
|
||||
followers =
|
||||
if actor.follower_address in activity.recipients do
|
||||
{:ok, followers} = User.get_followers(actor)
|
||||
Enum.filter(followers, &(!&1.local))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
|
||||
end
|
||||
|
||||
defp get_cc_ap_ids(ap_id, recipients) do
|
||||
host = Map.get(URI.parse(ap_id), :host)
|
||||
|
||||
recipients
|
||||
|> Enum.filter(fn %User{ap_id: ap_id} -> Map.get(URI.parse(ap_id), :host) == host end)
|
||||
|> Enum.map(& &1.ap_id)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Publishes an activity with BCC to all relevant peers.
|
||||
"""
|
||||
|
||||
def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bcc != [] do
|
||||
public = is_public?(activity)
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
|
||||
recipients = recipients(actor, activity)
|
||||
|
||||
recipients
|
||||
|> Enum.filter(&User.ap_enabled?/1)
|
||||
|> Enum.map(fn %{info: %{source_data: data}} -> data["inbox"] end)
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|> Instances.filter_reachable()
|
||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||
%User{ap_id: ap_id} =
|
||||
Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
|
||||
|
||||
cc = get_cc_ap_ids(ap_id, recipients)
|
||||
|
||||
json =
|
||||
data
|
||||
|> Map.put("cc", cc)
|
||||
|> Map.put("directMessage", true)
|
||||
|> Jason.encode!()
|
||||
|
||||
Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{
|
||||
inbox: inbox,
|
||||
json: json,
|
||||
actor: actor,
|
||||
id: activity.data["id"],
|
||||
unreachable_since: unreachable_since
|
||||
})
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Publishes an activity to all relevant peers.
|
||||
"""
|
||||
def publish(%User{} = actor, %Activity{} = activity) do
|
||||
public = is_public?(activity)
|
||||
|
||||
if public && Config.get([:instance, :allow_relay]) do
|
||||
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
||||
Relay.publish(activity)
|
||||
end
|
||||
|
||||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
|
||||
json = Jason.encode!(data)
|
||||
|
||||
recipients(actor, activity)
|
||||
|> Enum.filter(fn user -> User.ap_enabled?(user) end)
|
||||
|> Enum.map(fn %{info: %{source_data: data}} ->
|
||||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
|
||||
end)
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
|
||||
|> Instances.filter_reachable()
|
||||
|> Enum.each(fn {inbox, unreachable_since} ->
|
||||
Pleroma.Web.Federator.Publisher.enqueue_one(
|
||||
__MODULE__,
|
||||
%{
|
||||
inbox: inbox,
|
||||
json: json,
|
||||
actor: actor,
|
||||
id: activity.data["id"],
|
||||
unreachable_since: unreachable_since
|
||||
}
|
||||
)
|
||||
end)
|
||||
end
|
||||
|
||||
def gather_webfinger_links(%User{} = user) do
|
||||
[
|
||||
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
|
||||
%{
|
||||
"rel" => "self",
|
||||
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||
"href" => user.ap_id
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def gather_nodeinfo_protocol_names, do: ["activitypub"]
|
||||
end
|
||||
|
|
@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
|
||||
def follow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
%User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
|
||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
|
|
@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
|
||||
def unfollow(target_instance) do
|
||||
with %User{} = local_user <- get_actor(),
|
||||
%User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
|
||||
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def fix_implicit_addressing(object, _), do: object
|
||||
|
||||
def fix_addressing(object) do
|
||||
%User{} = user = User.get_or_fetch_by_ap_id(object["actor"])
|
||||
{:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
|
||||
followers_collection = User.ap_followers(user)
|
||||
|
||||
object
|
||||
|
|
@ -407,7 +407,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> fix_addressing
|
||||
|
||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
||||
object = fix_object(data["object"])
|
||||
|
||||
params = %{
|
||||
|
|
@ -436,7 +436,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data
|
||||
) do
|
||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||
{:user_blocked, false} <-
|
||||
|
|
@ -485,7 +485,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
|
|
@ -511,7 +511,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
%{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
|
|
@ -535,7 +535,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
%{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id),
|
||||
{:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
|
|
@ -548,7 +548,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
%{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id),
|
||||
public <- Visibility.is_public?(data),
|
||||
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
|
||||
|
|
@ -603,7 +603,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
object_id = Utils.get_ap_id(object_id)
|
||||
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id),
|
||||
:ok <- Containment.contain_origin(actor.ap_id, object.data),
|
||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||
|
|
@ -622,7 +622,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
} = data
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id),
|
||||
{:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
|
|
@ -640,7 +640,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
} = _data
|
||||
) do
|
||||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
|
||||
User.unfollow(follower, followed)
|
||||
{:ok, activity}
|
||||
|
|
@ -659,7 +659,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
) do
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
|
||||
%User{} = blocker <- User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
|
||||
User.unblock(blocker, blocked)
|
||||
{:ok, activity}
|
||||
|
|
@ -673,7 +673,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
) do
|
||||
with true <- Pleroma.Config.get([:activitypub, :accept_blocks]),
|
||||
%User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
||||
%User{} = blocker = User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
||||
User.unfollow(blocker, blocked)
|
||||
User.block(blocker, blocked)
|
||||
|
|
@ -692,7 +692,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
} = data
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id),
|
||||
{:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
|
|
@ -859,10 +859,16 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("tag", tags ++ mentions)
|
||||
end
|
||||
|
||||
def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
|
||||
user_info = add_emoji_tags(user_info)
|
||||
|
||||
object
|
||||
|> Map.put(:info, user_info)
|
||||
end
|
||||
|
||||
# TODO: we should probably send mtime instead of unix epoch time for updated
|
||||
def add_emoji_tags(object) do
|
||||
def add_emoji_tags(%{"emoji" => emoji} = object) do
|
||||
tags = object["tag"] || []
|
||||
emoji = object["emoji"] || []
|
||||
|
||||
out =
|
||||
emoji
|
||||
|
|
@ -880,6 +886,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
|> Map.put("tag", tags ++ out)
|
||||
end
|
||||
|
||||
def add_emoji_tags(object) do
|
||||
object
|
||||
end
|
||||
|
||||
def set_conversation(object) do
|
||||
Map.put(object, "conversation", object["context"])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -682,7 +682,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
"""
|
||||
def fetch_ordered_collection(from, pages_left, acc \\ []) do
|
||||
with {:ok, response} <- Tesla.get(from),
|
||||
{:ok, collection} <- Poison.decode(response.body) do
|
||||
{:ok, collection} <- Jason.decode(response.body) do
|
||||
case collection["type"] do
|
||||
"OrderedCollection" ->
|
||||
# If we've encountered the OrderedCollection and not the page,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,11 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
|
||||
endpoints = render("endpoints.json", %{user: user})
|
||||
|
||||
user_tags =
|
||||
user
|
||||
|> Transmogrifier.add_emoji_tags()
|
||||
|> Map.get("tag", [])
|
||||
|
||||
%{
|
||||
"id" => user.ap_id,
|
||||
"type" => "Person",
|
||||
|
|
@ -87,7 +92,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
|||
"publicKeyPem" => public_key
|
||||
},
|
||||
"endpoints" => endpoints,
|
||||
"tag" => user.info.source_data["tag"] || []
|
||||
"tag" => (user.info.source_data["tag"] || []) ++ user_tags
|
||||
}
|
||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
bio: "."
|
||||
}
|
||||
|
||||
changeset = User.register_changeset(%User{}, user_data, confirmed: true)
|
||||
changeset = User.register_changeset(%User{}, user_data, need_confirmation: false)
|
||||
{:ok, user} = User.register(changeset)
|
||||
|
||||
conn
|
||||
|
|
@ -101,7 +101,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
search_params = %{
|
||||
query: params["query"],
|
||||
page: page,
|
||||
page_size: page_size
|
||||
page_size: page_size,
|
||||
tags: params["tags"],
|
||||
name: params["name"],
|
||||
email: params["email"]
|
||||
}
|
||||
|
||||
with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
|
||||
|
|
@ -116,11 +119,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
)
|
||||
end
|
||||
|
||||
@filters ~w(local external active deactivated)
|
||||
|
||||
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
|
||||
@filters ~w(local external active deactivated is_admin is_moderator)
|
||||
|
||||
@spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
|
||||
defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
|
||||
|
||||
defp maybe_parse_filters(filters) do
|
||||
filters
|
||||
|> String.split(",")
|
||||
|
|
|
|||
|
|
@ -10,45 +10,23 @@ defmodule Pleroma.Web.AdminAPI.Search do
|
|||
|
||||
@page_size 50
|
||||
|
||||
def user(%{query: term} = params) when is_nil(term) or term == "" do
|
||||
query = maybe_filtered_query(params)
|
||||
defmacro not_empty_string(string) do
|
||||
quote do
|
||||
is_binary(unquote(string)) and unquote(string) != ""
|
||||
end
|
||||
end
|
||||
|
||||
@spec user(map()) :: {:ok, [User.t()], pos_integer()}
|
||||
def user(params \\ %{}) do
|
||||
query = User.Query.build(params) |> order_by([u], u.nickname)
|
||||
|
||||
paginated_query =
|
||||
maybe_filtered_query(params)
|
||||
|> paginate(params[:page] || 1, params[:page_size] || @page_size)
|
||||
User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)
|
||||
|
||||
count = query |> Repo.aggregate(:count, :id)
|
||||
count = Repo.aggregate(query, :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
|
||||
|
|
|
|||
|
|
@ -42,4 +42,30 @@ defmodule Pleroma.Web.Auth.Authenticator do
|
|||
implementation().oauth_consumer_template() ||
|
||||
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
|
||||
end
|
||||
|
||||
@doc "Gets user by nickname or email for auth."
|
||||
@spec fetch_user(String.t()) :: User.t() | nil
|
||||
def fetch_user(name) do
|
||||
User.get_by_nickname_or_email(name)
|
||||
end
|
||||
|
||||
# Gets name and password from conn
|
||||
#
|
||||
@spec fetch_credentials(Plug.Conn.t() | map()) ::
|
||||
{:ok, {name :: any, password :: any}} | {:error, :invalid_credentials}
|
||||
def fetch_credentials(%Plug.Conn{params: params} = _),
|
||||
do: fetch_credentials(params)
|
||||
|
||||
def fetch_credentials(params) do
|
||||
case params do
|
||||
%{"authorization" => %{"name" => name, "password" => password}} ->
|
||||
{:ok, {name, password}}
|
||||
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} ->
|
||||
{:ok, {name, password}}
|
||||
|
||||
_ ->
|
||||
{:error, :invalid_credentials}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
|||
|
||||
require Logger
|
||||
|
||||
import Pleroma.Web.Auth.Authenticator,
|
||||
only: [fetch_credentials: 1, fetch_user: 1]
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
@base Pleroma.Web.Auth.PleromaAuthenticator
|
||||
|
||||
|
|
@ -20,30 +23,20 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
|||
defdelegate oauth_consumer_template, to: @base
|
||||
|
||||
def get_user(%Plug.Conn{} = conn) do
|
||||
if Pleroma.Config.get([:ldap, :enabled]) do
|
||||
{name, password} =
|
||||
case conn.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)
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
|
||||
{:ok, {name, password}} <- fetch_credentials(conn),
|
||||
%User{} = user <- ldap_user(name, password) do
|
||||
{:ok, user}
|
||||
else
|
||||
# Fall back to default authenticator
|
||||
@base.get_user(conn)
|
||||
{:error, {:ldap_connection_error, _}} ->
|
||||
# When LDAP is unavailable, try default authenticator
|
||||
@base.get_user(conn)
|
||||
|
||||
{:ldap, _} ->
|
||||
@base.get_user(conn)
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -94,7 +87,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
|||
|
||||
case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
|
||||
:ok ->
|
||||
case User.get_by_nickname_or_email(name) do
|
||||
case fetch_user(name) do
|
||||
%User{} = user ->
|
||||
user
|
||||
|
||||
|
|
|
|||
|
|
@ -8,19 +8,14 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
import Pleroma.Web.Auth.Authenticator,
|
||||
only: [fetch_credentials: 1, fetch_user: 1]
|
||||
|
||||
@behaviour Pleroma.Web.Auth.Authenticator
|
||||
|
||||
def get_user(%Plug.Conn{} = conn) do
|
||||
{name, password} =
|
||||
case conn.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)},
|
||||
with {:ok, {name, password}} <- fetch_credentials(conn),
|
||||
{_, %User{} = user} <- {:user, fetch_user(name)},
|
||||
{_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do
|
||||
{:ok, user}
|
||||
else
|
||||
|
|
@ -79,7 +74,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
|||
password_confirmation: random_password
|
||||
},
|
||||
external: true,
|
||||
confirmed: true
|
||||
need_confirmation: false
|
||||
)
|
||||
|> Repo.insert(),
|
||||
{:ok, _} <-
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
defmodule Pleroma.Web.CommonAPI do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.ThreadMute
|
||||
|
|
@ -155,8 +156,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
|
||||
bcc <- bcc_for_list(user, visibility),
|
||||
context <- make_context(in_reply_to),
|
||||
cw <- data["spoiler_text"],
|
||||
full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
|
||||
cw <- data["spoiler_text"] || "",
|
||||
full_payload <- String.trim(status <> cw),
|
||||
length when length in 1..limit <- String.length(full_payload),
|
||||
object <-
|
||||
make_note_data(
|
||||
|
|
@ -174,10 +175,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
Map.put(
|
||||
object,
|
||||
"emoji",
|
||||
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
|
||||
|> Enum.reduce(%{}, fn {name, file, _}, acc ->
|
||||
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
|
||||
end)
|
||||
Formatter.get_emoji_map(full_payload)
|
||||
) do
|
||||
ActivityPub.create(
|
||||
%{
|
||||
|
|
@ -284,6 +282,15 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def bookmarked?(user, activity) do
|
||||
with %Bookmark{} <- Bookmark.get(user.id, activity.id) do
|
||||
true
|
||||
else
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def report(user, data) do
|
||||
with {:account_id, %{"account_id" => account_id}} <- {:account_id, data},
|
||||
{:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},
|
||||
|
|
|
|||
|
|
@ -190,6 +190,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
end).()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formatting text as BBCode.
|
||||
"""
|
||||
def format_input(text, "text/bbcode", options) do
|
||||
text
|
||||
|> String.replace(~r/\r/, "")
|
||||
|> Formatter.html_escape("text/plain")
|
||||
|> BBCode.to_html()
|
||||
|> (fn {:ok, html} -> html end).()
|
||||
|> Formatter.linkify(options)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formatting text to html.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@ defmodule Pleroma.Web.ControllerHelper do
|
|||
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
|
||||
Pleroma.Web.OAuth.parse_scopes(params["scope"] || params["scopes"], default)
|
||||
end
|
||||
|
||||
def json_response(conn, status, json) do
|
||||
conn
|
||||
|> put_status(status)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ defmodule Pleroma.Web.Endpoint do
|
|||
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
|
||||
)
|
||||
|
||||
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
|
||||
|
||||
plug(Plug.Static,
|
||||
at: "/pleroma/admin/",
|
||||
from: {:pleroma, "priv/static/adminfe/"}
|
||||
)
|
||||
|
||||
# Code reloading can be explicitly enabled under the
|
||||
# :code_reloader configuration of your endpoint.
|
||||
if code_reloading? do
|
||||
|
|
|
|||
|
|
@ -7,13 +7,10 @@ defmodule Pleroma.Web.Federator do
|
|||
alias Pleroma.Object.Containment
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.WebFinger
|
||||
alias Pleroma.Web.Websub
|
||||
|
||||
|
|
@ -42,14 +39,6 @@ defmodule Pleroma.Web.Federator do
|
|||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority)
|
||||
end
|
||||
|
||||
def publish_single_ap(params) do
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params])
|
||||
end
|
||||
|
||||
def publish_single_websub(websub) do
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub])
|
||||
end
|
||||
|
||||
def verify_websub(websub) do
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub])
|
||||
end
|
||||
|
|
@ -62,10 +51,6 @@ defmodule Pleroma.Web.Federator do
|
|||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions])
|
||||
end
|
||||
|
||||
def publish_single_salmon(params) do
|
||||
PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params])
|
||||
end
|
||||
|
||||
# Job Worker Callbacks
|
||||
|
||||
def perform(:refresh_subscriptions) do
|
||||
|
|
@ -95,23 +80,7 @@ defmodule Pleroma.Web.Federator do
|
|||
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
{:ok, actor} = WebFinger.ensure_keys_present(actor)
|
||||
|
||||
if Visibility.is_public?(activity) do
|
||||
if OStatus.is_representable?(activity) do
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end)
|
||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via Salmon" end)
|
||||
Pleroma.Web.Salmon.publish(actor, activity)
|
||||
end
|
||||
|
||||
if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do
|
||||
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end)
|
||||
Relay.publish(activity)
|
||||
end
|
||||
end
|
||||
|
||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
|
||||
Pleroma.Web.ActivityPub.ActivityPub.publish(actor, activity)
|
||||
Publisher.publish(actor, activity)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -148,25 +117,11 @@ defmodule Pleroma.Web.Federator do
|
|||
_e ->
|
||||
# Just drop those for now
|
||||
Logger.info("Unhandled activity")
|
||||
Logger.info(Poison.encode!(params, pretty: 2))
|
||||
Logger.info(Jason.encode!(params, pretty: true))
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def perform(:publish_single_salmon, params) do
|
||||
Salmon.send_to_user(params)
|
||||
end
|
||||
|
||||
def perform(:publish_single_ap, params) do
|
||||
case ActivityPub.publish_one(params) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
{:error, _} ->
|
||||
RetryQueue.enqueue(params, ActivityPub)
|
||||
end
|
||||
end
|
||||
|
||||
def perform(
|
||||
:publish_single_websub,
|
||||
%{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params
|
||||
|
|
|
|||
95
lib/pleroma/web/federator/publisher.ex
Normal file
95
lib/pleroma/web/federator/publisher.ex
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Federator.Publisher do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
|
||||
require Logger
|
||||
|
||||
@moduledoc """
|
||||
Defines the contract used by federation implementations to publish messages to
|
||||
their peers.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Determine whether an activity can be relayed using the federation module.
|
||||
"""
|
||||
@callback is_representable?(Pleroma.Activity.t()) :: boolean()
|
||||
|
||||
@doc """
|
||||
Relays an activity to a specified peer, determined by the parameters. The
|
||||
parameters used are controlled by the federation module.
|
||||
"""
|
||||
@callback publish_one(Map.t()) :: {:ok, Map.t()} | {:error, any()}
|
||||
|
||||
@doc """
|
||||
Enqueue publishing a single activity.
|
||||
"""
|
||||
@spec enqueue_one(module(), Map.t()) :: :ok
|
||||
def enqueue_one(module, %{} = params),
|
||||
do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params])
|
||||
|
||||
@spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}
|
||||
def perform(:publish_one, module, params) do
|
||||
case apply(module, :publish_one, [params]) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
{:error, _e} ->
|
||||
RetryQueue.enqueue(params, module)
|
||||
end
|
||||
end
|
||||
|
||||
def perform(type, _, _) do
|
||||
Logger.debug("Unknown task: #{type}")
|
||||
{:error, "Don't know what to do with this"}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Relays an activity to all specified peers.
|
||||
"""
|
||||
@callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()}
|
||||
|
||||
@spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok
|
||||
def publish(%User{} = user, %Activity{} = activity) do
|
||||
Config.get([:instance, :federation_publisher_modules])
|
||||
|> Enum.each(fn module ->
|
||||
if module.is_representable?(activity) do
|
||||
Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}")
|
||||
module.publish(user, activity)
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gathers links used by an outgoing federation module for WebFinger output.
|
||||
"""
|
||||
@callback gather_webfinger_links(Pleroma.User.t()) :: list()
|
||||
|
||||
@spec gather_webfinger_links(Pleroma.User.t()) :: list()
|
||||
def gather_webfinger_links(%User{} = user) do
|
||||
Config.get([:instance, :federation_publisher_modules])
|
||||
|> Enum.reduce([], fn module, links ->
|
||||
links ++ module.gather_webfinger_links(user)
|
||||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gathers nodeinfo protocol names supported by the federation module.
|
||||
"""
|
||||
@callback gather_nodeinfo_protocol_names() :: list()
|
||||
|
||||
@spec gather_nodeinfo_protocol_names() :: list()
|
||||
def gather_nodeinfo_protocol_names do
|
||||
Config.get([:instance, :federation_publisher_modules])
|
||||
|> Enum.reduce([], fn module, links ->
|
||||
links ++ module.gather_nodeinfo_protocol_names()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -6,8 +6,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
use Pleroma.Web, :controller
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Filter
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Object.Fetcher
|
||||
|
|
@ -22,6 +25,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.AppView
|
||||
alias Pleroma.Web.MastodonAPI.ConversationView
|
||||
alias Pleroma.Web.MastodonAPI.FilterView
|
||||
alias Pleroma.Web.MastodonAPI.ListView
|
||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||
|
|
@ -33,20 +37,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Web.MediaProxy
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
|
||||
plug(
|
||||
Pleroma.Plugs.RateLimitPlug,
|
||||
%{
|
||||
max_requests: Config.get([:app_account_creation, :max_requests]),
|
||||
interval: Config.get([:app_account_creation, :interval])
|
||||
}
|
||||
when action in [:account_register]
|
||||
)
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
def create_app(conn, params) do
|
||||
scopes = oauth_scopes(params, ["read"])
|
||||
scopes = Scopes.fetch_scopes(params, ["read"])
|
||||
|
||||
app_attrs =
|
||||
params
|
||||
|
|
@ -85,7 +100,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
user_params =
|
||||
%{}
|
||||
|> add_if_present(params, "display_name", :name)
|
||||
|> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value)} end)
|
||||
|> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
|
||||
|> add_if_present(params, "avatar", :avatar, fn value ->
|
||||
with %Plug.Upload{} <- value,
|
||||
{:ok, object} <- ActivityPub.upload(value, type: :avatar) do
|
||||
|
|
@ -95,9 +110,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end)
|
||||
|
||||
emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
|
||||
|
||||
user_info_emojis =
|
||||
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|
||||
|> Enum.dedup()
|
||||
|
||||
info_params =
|
||||
%{}
|
||||
|> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end)
|
||||
[:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
|
||||
|> Enum.reduce(%{}, fn key, acc ->
|
||||
add_if_present(acc, params, to_string(key), key, fn value ->
|
||||
{:ok, ControllerHelper.truthy_param?(value)}
|
||||
end)
|
||||
end)
|
||||
|> add_if_present(params, "default_scope", :default_scope)
|
||||
|> add_if_present(params, "header", :banner, fn value ->
|
||||
with %Plug.Upload{} <- value,
|
||||
{:ok, object} <- ActivityPub.upload(value, type: :banner) do
|
||||
|
|
@ -106,8 +132,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
_ -> :error
|
||||
end
|
||||
end)
|
||||
|> Map.put(:emoji, user_info_emojis)
|
||||
|
||||
info_cng = User.Info.mastodon_profile_update(user.info, info_params)
|
||||
info_cng = User.Info.profile_update(user.info, info_params)
|
||||
|
||||
with changeset <- User.update_changeset(user, user_params),
|
||||
changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
|
||||
|
|
@ -151,7 +178,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
@mastodon_api_level "2.5.0"
|
||||
@mastodon_api_level "2.7.2"
|
||||
|
||||
def masto_instance(conn, _params) do
|
||||
instance = Config.get(:instance)
|
||||
|
|
@ -545,10 +572,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||
%Object{} = object <- Object.normalize(activity),
|
||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||
true <- Visibility.visible_for_user?(activity, user),
|
||||
{:ok, user} <- User.bookmark(user, object.data["id"]) do
|
||||
{:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
|
|
@ -557,10 +583,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||
%Object{} = object <- Object.normalize(activity),
|
||||
%User{} = user <- User.get_cached_by_nickname(user.nickname),
|
||||
true <- Visibility.visible_for_user?(activity, user),
|
||||
{:ok, user} <- User.unbookmark(user, object.data["id"]) do
|
||||
{:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
|
|
@ -683,7 +708,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def favourited_by(conn, %{"id" => id}) do
|
||||
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
|
||||
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do
|
||||
q = from(u in User, where: u.ap_id in ^likes)
|
||||
|
|
@ -691,13 +716,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render(AccountView, "accounts.json", %{users: users, as: :user})
|
||||
|> render(AccountView, "accounts.json", %{for: user, users: users, as: :user})
|
||||
else
|
||||
_ -> json(conn, [])
|
||||
end
|
||||
end
|
||||
|
||||
def reblogged_by(conn, %{"id" => id}) do
|
||||
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),
|
||||
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
|
||||
q = from(u in User, where: u.ap_id in ^announces)
|
||||
|
|
@ -705,7 +730,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: users, as: :user})
|
||||
|> render("accounts.json", %{for: user, users: users, as: :user})
|
||||
else
|
||||
_ -> json(conn, [])
|
||||
end
|
||||
|
|
@ -762,7 +787,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
conn
|
||||
|> add_link_headers(:followers, followers, user)
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: followers, as: :user})
|
||||
|> render("accounts.json", %{for: for_user, users: followers, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -779,7 +804,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
conn
|
||||
|> add_link_headers(:following, followers, user)
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: followers, as: :user})
|
||||
|> render("accounts.json", %{for: for_user, users: followers, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -787,7 +812,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
with {:ok, follow_requests} <- User.get_follow_requests(followed) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: follow_requests, as: :user})
|
||||
|> render("accounts.json", %{for: followed, users: follow_requests, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1124,15 +1149,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def bookmarks(%{assigns: %{user: user}} = conn, _) do
|
||||
def bookmarks(%{assigns: %{user: user}} = conn, params) do
|
||||
user = User.get_cached_by_id(user.id)
|
||||
|
||||
bookmarks =
|
||||
Bookmark.for_user_query(user.id)
|
||||
|> Pagination.fetch_paginated(params)
|
||||
|
||||
activities =
|
||||
user.bookmarks
|
||||
|> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end)
|
||||
|> Enum.reverse()
|
||||
bookmarks
|
||||
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
|
||||
|
||||
conn
|
||||
|> add_link_headers(:bookmarks, bookmarks)
|
||||
|> put_view(StatusView)
|
||||
|> render("index.json", %{activities: activities, for: user, as: :activity})
|
||||
end
|
||||
|
|
@ -1207,7 +1236,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
{:ok, users} = Pleroma.List.get_following(list) do
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render("accounts.json", %{users: users, as: :user})
|
||||
|> render("accounts.json", %{for: user, users: users, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -1265,8 +1294,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
initial_state =
|
||||
%{
|
||||
meta: %{
|
||||
streaming_api_base_url:
|
||||
String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
|
||||
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
|
||||
access_token: token,
|
||||
locale: "en",
|
||||
domain: Pleroma.Web.Endpoint.host(),
|
||||
|
|
@ -1518,7 +1546,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
user_id: user.id,
|
||||
phrase: phrase,
|
||||
context: context,
|
||||
hide: Map.get(params, "irreversible", nil),
|
||||
hide: Map.get(params, "irreversible", false),
|
||||
whole_word: Map.get(params, "boolean", true)
|
||||
# expires_at
|
||||
}
|
||||
|
|
@ -1623,7 +1651,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
x,
|
||||
"id",
|
||||
case User.get_or_fetch(x["acct"]) do
|
||||
%{id: id} -> id
|
||||
{:ok, %User{id: id}} -> id
|
||||
_ -> 0
|
||||
end
|
||||
)
|
||||
|
|
@ -1675,6 +1703,78 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def account_register(
|
||||
%{assigns: %{app: app}} = conn,
|
||||
%{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
|
||||
) do
|
||||
params =
|
||||
params
|
||||
|> Map.take([
|
||||
"email",
|
||||
"captcha_solution",
|
||||
"captcha_token",
|
||||
"captcha_answer_data",
|
||||
"token",
|
||||
"password"
|
||||
])
|
||||
|> Map.put("nickname", nickname)
|
||||
|> Map.put("fullname", params["fullname"] || nickname)
|
||||
|> Map.put("bio", params["bio"] || "")
|
||||
|> Map.put("confirm", params["password"])
|
||||
|
||||
with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
|
||||
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
|
||||
json(conn, %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
scope: app.scopes,
|
||||
created_at: Token.Utils.format_created_at(token)
|
||||
})
|
||||
else
|
||||
{:error, errors} ->
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json(Jason.encode!(errors))
|
||||
end
|
||||
end
|
||||
|
||||
def account_register(%{assigns: %{app: _app}} = conn, _params) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json(%{error: "Missing parameters"})
|
||||
end
|
||||
|
||||
def account_register(conn, _) do
|
||||
conn
|
||||
|> put_status(403)
|
||||
|> json(%{error: "Invalid credentials"})
|
||||
end
|
||||
|
||||
def conversations(%{assigns: %{user: user}} = conn, params) do
|
||||
participations = Participation.for_user_with_last_activity_id(user, params)
|
||||
|
||||
conversations =
|
||||
Enum.map(participations, fn participation ->
|
||||
ConversationView.render("participation.json", %{participation: participation, user: user})
|
||||
end)
|
||||
|
||||
conn
|
||||
|> add_link_headers(:conversations, participations)
|
||||
|> json(conversations)
|
||||
end
|
||||
|
||||
def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
|
||||
with %Participation{} = participation <-
|
||||
Repo.get_by(Participation, id: participation_id, user_id: user.id),
|
||||
{:ok, participation} <- Participation.mark_as_read(participation) do
|
||||
participation_view =
|
||||
ConversationView.render("participation.json", %{participation: participation, user: user})
|
||||
|
||||
conn
|
||||
|> json(participation_view)
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, target, params)
|
||||
when is_binary(target) do
|
||||
res = render(conn, target, params)
|
||||
|
|
|
|||
|
|
@ -113,21 +113,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
bot: bot,
|
||||
source: %{
|
||||
note: "",
|
||||
privacy: user_info.default_scope,
|
||||
sensitive: false
|
||||
sensitive: false,
|
||||
pleroma: %{}
|
||||
},
|
||||
|
||||
# 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
|
||||
}
|
||||
|> with_notification_settings(user, opts[:for])
|
||||
pleroma: %{
|
||||
confirmation_pending: user_info.confirmation_pending,
|
||||
tags: user.tags,
|
||||
hide_followers: user.info.hide_followers,
|
||||
hide_follows: user.info.hide_follows,
|
||||
hide_favorites: user.info.hide_favorites,
|
||||
relationship: relationship
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|> maybe_put_settings(user, opts[:for], user_info)
|
||||
|> maybe_put_notification_settings(user, opts[:for])
|
||||
end
|
||||
|
||||
defp username_from_nickname(string) when is_binary(string) do
|
||||
|
|
@ -136,9 +138,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
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)
|
||||
defp maybe_put_settings(
|
||||
data,
|
||||
%User{id: user_id} = user,
|
||||
%User{id: user_id},
|
||||
user_info
|
||||
) do
|
||||
data
|
||||
|> Kernel.put_in([:source, :privacy], user_info.default_scope)
|
||||
|> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role)
|
||||
|> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text)
|
||||
end
|
||||
|
||||
defp with_notification_settings(data, _, _), do: data
|
||||
defp maybe_put_settings(data, _, _, _), do: data
|
||||
|
||||
defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
|
||||
data
|
||||
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|
||||
|> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
|
||||
end
|
||||
|
||||
defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
data
|
||||
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|
||||
|> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
|
||||
end
|
||||
|
||||
defp maybe_put_role(data, _, _), do: data
|
||||
|
||||
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
|
||||
Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)
|
||||
end
|
||||
|
||||
defp maybe_put_notification_settings(data, _, _), do: data
|
||||
end
|
||||
|
|
|
|||
38
lib/pleroma/web/mastodon_api/views/conversation_view.ex
Normal file
38
lib/pleroma/web/mastodon_api/views/conversation_view.ex
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
defmodule Pleroma.Web.MastodonAPI.ConversationView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
def render("participation.json", %{participation: participation, user: user}) do
|
||||
participation = Repo.preload(participation, conversation: :users)
|
||||
|
||||
last_activity_id =
|
||||
with nil <- participation.last_activity_id do
|
||||
ActivityPub.fetch_latest_activity_id_for_context(participation.conversation.ap_id, %{
|
||||
"user" => user,
|
||||
"blocking_user" => user
|
||||
})
|
||||
end
|
||||
|
||||
activity = Activity.get_by_id_with_object(last_activity_id)
|
||||
|
||||
last_status = StatusView.render("status.json", %{activity: activity, for: user})
|
||||
|
||||
accounts =
|
||||
AccountView.render("accounts.json", %{
|
||||
users: participation.conversation.users,
|
||||
as: :user
|
||||
})
|
||||
|
||||
%{
|
||||
id: participation.id |> to_string(),
|
||||
accounts: accounts,
|
||||
unread: !participation.read,
|
||||
last_status: last_status
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -75,17 +75,22 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
def render(
|
||||
"status.json",
|
||||
%{activity: %{data: %{"type" => "Announce", "object" => object}} = activity} = opts
|
||||
%{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts
|
||||
) do
|
||||
user = get_user(activity.data["actor"])
|
||||
created_at = Utils.to_masto_date(activity.data["published"])
|
||||
activity_object = Object.normalize(activity)
|
||||
|
||||
reblogged_activity =
|
||||
Activity.create_by_object_ap_id(activity_object.data["id"])
|
||||
|> Activity.with_preloaded_bookmark(opts[:for])
|
||||
|> Repo.one()
|
||||
|
||||
reblogged_activity = Activity.get_create_by_object_ap_id(object)
|
||||
reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity))
|
||||
|
||||
activity_object = Object.normalize(activity)
|
||||
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
|
||||
bookmarked = opts[:for] && activity_object.data["id"] in opts[:for].bookmarks
|
||||
|
||||
bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil
|
||||
|
||||
mentions =
|
||||
activity.recipients
|
||||
|
|
@ -95,8 +100,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
%{
|
||||
id: to_string(activity.id),
|
||||
uri: object,
|
||||
url: object,
|
||||
uri: activity_object.data["id"],
|
||||
url: activity_object.data["id"],
|
||||
account: AccountView.render("account.json", %{user: user}),
|
||||
in_reply_to_id: nil,
|
||||
in_reply_to_account_id: nil,
|
||||
|
|
@ -148,7 +153,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
|||
|
||||
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
|
||||
|
||||
bookmarked = opts[:for] && object.data["id"] in opts[:for].bookmarks
|
||||
bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
|
||||
|
||||
attachment_data = object.data["attachment"] || []
|
||||
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
|
||||
|
|
|
|||
|
|
@ -13,32 +13,44 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
|
||||
def url(url) do
|
||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||
domain = URI.parse(url).host
|
||||
|
||||
if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) do
|
||||
url
|
||||
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
|
||||
replacement = get_replacement(url, ":2F:")
|
||||
|
||||
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
|
||||
base64 =
|
||||
cond do
|
||||
!Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) ->
|
||||
url
|
||||
|> String.replace("%2F", replacement)
|
||||
|> URI.decode()
|
||||
|> URI.encode()
|
||||
|> String.replace(replacement, "%2F")
|
||||
|> Base.url_encode64(@base64_opts)
|
||||
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern ->
|
||||
String.equivalent?(domain, pattern)
|
||||
end) ->
|
||||
url
|
||||
|
||||
build_url(sig64, base64, filename(url))
|
||||
true ->
|
||||
encode_url(url)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_url(url) do
|
||||
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
|
||||
replacement = get_replacement(url, ":2F:")
|
||||
|
||||
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice.
|
||||
base64 =
|
||||
url
|
||||
|> String.replace("%2F", replacement)
|
||||
|> URI.decode()
|
||||
|> URI.encode()
|
||||
|> String.replace(replacement, "%2F")
|
||||
|> Base.url_encode64(@base64_opts)
|
||||
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
|
||||
build_url(sig64, base64, filename(url))
|
||||
end
|
||||
|
||||
def decode_url(sig, url) do
|
||||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]
|
||||
sig = Base.url_decode64!(sig, @base64_opts)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug)
|
||||
|
||||
|
|
@ -137,7 +138,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
name: Pleroma.Application.name() |> String.downcase(),
|
||||
version: Pleroma.Application.version()
|
||||
},
|
||||
protocols: ["ostatus", "activitypub"],
|
||||
protocols: Publisher.gather_nodeinfo_protocol_names(),
|
||||
services: %{
|
||||
inbound: [],
|
||||
outbound: []
|
||||
|
|
|
|||
|
|
@ -3,18 +3,4 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth do
|
||||
def parse_scopes(scopes, _default) when is_list(scopes) do
|
||||
Enum.filter(scopes, &(&1 not in [nil, ""]))
|
||||
end
|
||||
|
||||
def parse_scopes(scopes, default) when is_binary(scopes) do
|
||||
scopes
|
||||
|> String.trim()
|
||||
|> String.split(~r/[\s,]+/)
|
||||
|> parse_scopes(default)
|
||||
end
|
||||
|
||||
def parse_scopes(_, default) do
|
||||
default
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.Web.OAuth.App do
|
|||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "apps" do
|
||||
field(:client_name, :string)
|
||||
field(:redirect_uris, :string)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "oauth_authorizations" do
|
||||
field(:token, :string)
|
||||
field(:scopes, {:array, :string}, default: [])
|
||||
|
|
@ -24,28 +26,45 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
@spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) ::
|
||||
{:ok, Authorization.t()} | {:error, Changeset.t()}
|
||||
def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
|
||||
scopes = scopes || app.scopes
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||
|
||||
authorization = %Authorization{
|
||||
token: token,
|
||||
used: false,
|
||||
%{
|
||||
scopes: scopes || app.scopes,
|
||||
user_id: user.id,
|
||||
app_id: app.id,
|
||||
scopes: scopes,
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
|
||||
app_id: app.id
|
||||
}
|
||||
|
||||
Repo.insert(authorization)
|
||||
|> create_changeset()
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec create_changeset(map()) :: Changeset.t()
|
||||
def create_changeset(attrs \\ %{}) do
|
||||
%Authorization{}
|
||||
|> cast(attrs, [:user_id, :app_id, :scopes, :valid_until])
|
||||
|> validate_required([:app_id, :scopes])
|
||||
|> add_token()
|
||||
|> add_lifetime()
|
||||
end
|
||||
|
||||
defp add_token(changeset) do
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||
put_change(changeset, :token, token)
|
||||
end
|
||||
|
||||
defp add_lifetime(changeset) do
|
||||
put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10))
|
||||
end
|
||||
|
||||
@spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t()
|
||||
def use_changeset(%Authorization{} = auth, params) do
|
||||
auth
|
||||
|> cast(params, [:used])
|
||||
|> validate_required([:used])
|
||||
end
|
||||
|
||||
@spec use_token(Authorization.t()) ::
|
||||
{:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()}
|
||||
def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
|
||||
if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
|
||||
Repo.update(use_changeset(auth, %{used: true}))
|
||||
|
|
@ -56,6 +75,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
|
||||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||
|
||||
@spec delete_user_authorizations(User.t()) :: {integer(), any()}
|
||||
def delete_user_authorizations(%User{id: user_id}) do
|
||||
from(
|
||||
a in Pleroma.Web.OAuth.Authorization,
|
||||
|
|
@ -63,4 +83,11 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
)
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
@doc "gets auth for app by token"
|
||||
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
def get_by_token(%App{id: app_id} = _app, token) do
|
||||
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
|
||||
|> Repo.find_resource()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,11 +13,14 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
|
||||
alias Pleroma.Web.OAuth.Scopes
|
||||
|
||||
if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
|
||||
|
||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
|
||||
plug(:fetch_session)
|
||||
plug(:fetch_flash)
|
||||
|
||||
|
|
@ -53,7 +56,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
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
|
||||
scopes = Scopes.fetch_scopes(params, available_scopes)
|
||||
|
||||
# Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
|
||||
render(conn, Authenticator.auth_template(), %{
|
||||
|
|
@ -109,7 +112,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|
||||
defp handle_create_authorization_error(
|
||||
conn,
|
||||
{scopes_issue, _},
|
||||
{:error, scopes_issue},
|
||||
%{"authorization" => _} = params
|
||||
)
|
||||
when scopes_issue in [:unsupported_scopes, :missing_scopes] do
|
||||
|
|
@ -138,25 +141,33 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
@doc "Renew access_token with refresh_token"
|
||||
def token_exchange(
|
||||
conn,
|
||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = params
|
||||
) do
|
||||
with %App{} = app <- get_app_from_request(conn, params),
|
||||
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
|
||||
{:ok, token} <- RefreshToken.grant(token) do
|
||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||
|
||||
json(conn, response_token(user, token, response_attrs))
|
||||
else
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|> json(%{error: "Invalid credentials"})
|
||||
end
|
||||
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),
|
||||
fixed_token = Token.Utils.fix_padding(params["code"]),
|
||||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||
%User{} = user <- User.get_cached_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 = %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
created_at: DateTime.to_unix(inserted_at),
|
||||
expires_in: 60 * 10,
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
|
||||
|
||||
json(conn, response)
|
||||
json(conn, response_token(user, token, response_attrs))
|
||||
else
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|
|
@ -172,21 +183,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
%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),
|
||||
{:ok, scopes} <- validate_scopes(app, params),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
response = %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: 60 * 10,
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
json(conn, response_token(user, token))
|
||||
else
|
||||
{:auth_active, false} ->
|
||||
# Per https://github.com/tootsuite/mastodon/blob/
|
||||
|
|
@ -218,10 +218,34 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
token_exchange(conn, params)
|
||||
end
|
||||
|
||||
def token_revoke(conn, %{"token" => token} = params) do
|
||||
def token_exchange(conn, %{"grant_type" => "client_credentials"} = params) do
|
||||
with %App{} = app <- get_app_from_request(conn, params),
|
||||
%Token{} = token <- Repo.get_by(Token, token: token, app_id: app.id),
|
||||
{:ok, %Token{}} <- Repo.delete(token) do
|
||||
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||
{:ok, token} <- Token.exchange_token(app, auth),
|
||||
{:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do
|
||||
response = %{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
created_at: DateTime.to_unix(inserted_at),
|
||||
expires_in: 60 * 10,
|
||||
scope: Enum.join(token.scopes, " ")
|
||||
}
|
||||
|
||||
json(conn, response)
|
||||
else
|
||||
_error ->
|
||||
put_status(conn, 400)
|
||||
|> json(%{error: "Invalid credentials"})
|
||||
end
|
||||
end
|
||||
|
||||
# Bad request
|
||||
def token_exchange(conn, params), do: bad_request(conn, params)
|
||||
|
||||
def token_revoke(conn, %{"token" => _token} = params) do
|
||||
with %App{} = app <- get_app_from_request(conn, params),
|
||||
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
||||
json(conn, %{})
|
||||
else
|
||||
_error ->
|
||||
|
|
@ -230,17 +254,27 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
def token_revoke(conn, params), do: bad_request(conn, params)
|
||||
|
||||
# Response for bad request
|
||||
defp bad_request(conn, _) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(%{error: "Bad request"})
|
||||
end
|
||||
|
||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||
def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do
|
||||
scope =
|
||||
oauth_scopes(auth_attrs, [])
|
||||
|> Enum.join(" ")
|
||||
auth_attrs
|
||||
|> Scopes.fetch_scopes([])
|
||||
|> Scopes.to_string()
|
||||
|
||||
state =
|
||||
auth_attrs
|
||||
|> Map.delete("scopes")
|
||||
|> Map.put("scope", scope)
|
||||
|> Poison.encode!()
|
||||
|> Jason.encode!()
|
||||
|
||||
params =
|
||||
auth_attrs
|
||||
|
|
@ -278,25 +312,22 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
params = callback_params(params)
|
||||
|
||||
with {:ok, registration} <- Authenticator.get_registration(conn) do
|
||||
user = Repo.preload(registration, :user).user
|
||||
auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
|
||||
|
||||
if user do
|
||||
create_authorization(
|
||||
conn,
|
||||
%{"authorization" => auth_attrs},
|
||||
user: user
|
||||
)
|
||||
else
|
||||
registration_params =
|
||||
Map.merge(auth_attrs, %{
|
||||
"nickname" => Registration.nickname(registration),
|
||||
"email" => Registration.email(registration)
|
||||
})
|
||||
case Repo.get_assoc(registration, :user) do
|
||||
{:ok, user} ->
|
||||
create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
|
||||
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> registration_details(%{"authorization" => registration_params})
|
||||
_ ->
|
||||
registration_params =
|
||||
Map.merge(auth_attrs, %{
|
||||
"nickname" => Registration.nickname(registration),
|
||||
"email" => Registration.email(registration)
|
||||
})
|
||||
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> registration_details(%{"authorization" => registration_params})
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
|
|
@ -307,7 +338,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
defp callback_params(%{"state" => state} = params) do
|
||||
Map.merge(params, Poison.decode!(state))
|
||||
Map.merge(params, Jason.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(conn, %{"authorization" => auth_attrs}) do
|
||||
|
|
@ -315,7 +346,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
client_id: auth_attrs["client_id"],
|
||||
redirect_uri: auth_attrs["redirect_uri"],
|
||||
state: auth_attrs["state"],
|
||||
scopes: oauth_scopes(auth_attrs, []),
|
||||
scopes: Scopes.fetch_scopes(auth_attrs, []),
|
||||
nickname: auth_attrs["nickname"],
|
||||
email: auth_attrs["email"]
|
||||
})
|
||||
|
|
@ -390,45 +421,36 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
{:get_user, (user && {:ok, 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_attrs, []),
|
||||
{:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
|
||||
# Note: `scope` param is intentionally not optional in this context
|
||||
{:missing_scopes, false} <- {:missing_scopes, scopes == []},
|
||||
{:ok, scopes} <- validate_scopes(app, auth_attrs),
|
||||
{: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
|
||||
token
|
||||
|> URI.decode()
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|> Base.url_encode64(padding: false)
|
||||
defp get_app_from_request(conn, params) do
|
||||
conn
|
||||
|> fetch_client_credentials(params)
|
||||
|> fetch_client
|
||||
end
|
||||
|
||||
defp get_app_from_request(conn, params) do
|
||||
# Per RFC 6749, HTTP Basic is preferred to body params
|
||||
{client_id, client_secret} =
|
||||
with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
|
||||
{:ok, decoded} <- Base.decode64(encoded),
|
||||
[id, secret] <-
|
||||
String.split(decoded, ":")
|
||||
|> Enum.map(fn s -> URI.decode_www_form(s) end) do
|
||||
{id, secret}
|
||||
else
|
||||
_ -> {params["client_id"], params["client_secret"]}
|
||||
end
|
||||
defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
|
||||
Repo.get_by(App, client_id: id, client_secret: secret)
|
||||
end
|
||||
|
||||
if client_id && client_secret do
|
||||
Repo.get_by(
|
||||
App,
|
||||
client_id: client_id,
|
||||
client_secret: client_secret
|
||||
)
|
||||
defp fetch_client({_id, _secret}), do: nil
|
||||
|
||||
defp fetch_client_credentials(conn, params) do
|
||||
# Per RFC 6749, HTTP Basic is preferred to body params
|
||||
with ["Basic " <> encoded] <- get_req_header(conn, "authorization"),
|
||||
{:ok, decoded} <- Base.decode64(encoded),
|
||||
[id, secret] <-
|
||||
Enum.map(
|
||||
String.split(decoded, ":"),
|
||||
fn s -> URI.decode_www_form(s) end
|
||||
) do
|
||||
{id, secret}
|
||||
else
|
||||
nil
|
||||
_ -> {params["client_id"], params["client_secret"]}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -441,4 +463,24 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|
||||
defp put_session_registration_id(conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
|
||||
defp response_token(%User{} = user, token, opts \\ %{}) do
|
||||
%{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: @expires_in,
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|> Map.merge(opts)
|
||||
end
|
||||
|
||||
@spec validate_scopes(App.t(), map()) ::
|
||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||
defp validate_scopes(app, params) do
|
||||
params
|
||||
|> Scopes.fetch_scopes(app.scopes)
|
||||
|> Scopes.validates(app.scopes)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
67
lib/pleroma/web/oauth/scopes.ex
Normal file
67
lib/pleroma/web/oauth/scopes.ex
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.OAuth.Scopes do
|
||||
@moduledoc """
|
||||
Functions for dealing with scopes.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Fetch scopes from requiest params.
|
||||
|
||||
Note: `scopes` is used by Mastodon — supporting it but sticking to
|
||||
OAuth's standard `scope` wherever we control it
|
||||
"""
|
||||
@spec fetch_scopes(map(), list()) :: list()
|
||||
def fetch_scopes(params, default) do
|
||||
parse_scopes(params["scope"] || params["scopes"], default)
|
||||
end
|
||||
|
||||
def parse_scopes(scopes, _default) when is_list(scopes) do
|
||||
Enum.filter(scopes, &(&1 not in [nil, ""]))
|
||||
end
|
||||
|
||||
def parse_scopes(scopes, default) when is_binary(scopes) do
|
||||
scopes
|
||||
|> to_list
|
||||
|> parse_scopes(default)
|
||||
end
|
||||
|
||||
def parse_scopes(_, default) do
|
||||
default
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert scopes string to list
|
||||
"""
|
||||
@spec to_list(binary()) :: [binary()]
|
||||
def to_list(nil), do: []
|
||||
|
||||
def to_list(str) do
|
||||
str
|
||||
|> String.trim()
|
||||
|> String.split(~r/[\s,]+/)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Convert scopes list to string
|
||||
"""
|
||||
@spec to_string(list()) :: binary()
|
||||
def to_string(scopes), do: Enum.join(scopes, " ")
|
||||
|
||||
@doc """
|
||||
Validates scopes.
|
||||
"""
|
||||
@spec validates(list() | nil, list()) ::
|
||||
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
|
||||
def validates([], _app_scopes), do: {:error, :missing_scopes}
|
||||
def validates(nil, _app_scopes), do: {:error, :missing_scopes}
|
||||
|
||||
def validates(scopes, app_scopes) do
|
||||
case scopes -- app_scopes do
|
||||
[] -> {:ok, scopes}
|
||||
_ -> {:error, :unsupported_scopes}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
use Ecto.Schema
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -13,6 +14,9 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
alias Pleroma.Web.OAuth.Authorization
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "oauth_tokens" do
|
||||
field(:token, :string)
|
||||
field(:refresh_token, :string)
|
||||
|
|
@ -24,28 +28,72 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
timestamps()
|
||||
end
|
||||
|
||||
@doc "Gets token for app by access token"
|
||||
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
def get_by_token(%App{id: app_id} = _app, token) do
|
||||
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
|
||||
|> Repo.find_resource()
|
||||
end
|
||||
|
||||
@doc "Gets token for app by refresh token"
|
||||
@spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
def get_by_refresh_token(%App{id: app_id} = _app, token) do
|
||||
from(t in __MODULE__,
|
||||
where: t.app_id == ^app_id and t.refresh_token == ^token,
|
||||
preload: [:user]
|
||||
)
|
||||
|> Repo.find_resource()
|
||||
end
|
||||
|
||||
@spec exchange_token(App.t(), Authorization.t()) ::
|
||||
{:ok, Token.t()} | {:error, Changeset.t()}
|
||||
def exchange_token(app, auth) do
|
||||
with {:ok, auth} <- Authorization.use_token(auth),
|
||||
true <- auth.app_id == app.id do
|
||||
create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes)
|
||||
user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{}
|
||||
|
||||
create_token(
|
||||
app,
|
||||
user,
|
||||
%{scopes: auth.scopes}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def create_token(%App{} = app, %User{} = user, scopes \\ nil) do
|
||||
scopes = scopes || app.scopes
|
||||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
|
||||
defp put_token(changeset) do
|
||||
changeset
|
||||
|> change(%{token: Token.Utils.generate_token()})
|
||||
|> validate_required([:token])
|
||||
|> unique_constraint(:token)
|
||||
end
|
||||
|
||||
token = %Token{
|
||||
token: token,
|
||||
refresh_token: refresh_token,
|
||||
scopes: scopes,
|
||||
user_id: user.id,
|
||||
app_id: app.id,
|
||||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)
|
||||
}
|
||||
defp put_refresh_token(changeset, attrs) do
|
||||
refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_token())
|
||||
|
||||
Repo.insert(token)
|
||||
changeset
|
||||
|> change(%{refresh_token: refresh_token})
|
||||
|> validate_required([:refresh_token])
|
||||
|> unique_constraint(:refresh_token)
|
||||
end
|
||||
|
||||
defp put_valid_until(changeset, attrs) do
|
||||
expires_in =
|
||||
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), @expires_in))
|
||||
|
||||
changeset
|
||||
|> change(%{valid_until: expires_in})
|
||||
|> validate_required([:valid_until])
|
||||
end
|
||||
|
||||
@spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
|
||||
def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
|
||||
%__MODULE__{user_id: user.id, app_id: app.id}
|
||||
|> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
|
||||
|> validate_required([:scopes, :app_id])
|
||||
|> put_valid_until(attrs)
|
||||
|> put_token()
|
||||
|> put_refresh_token(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def delete_user_tokens(%User{id: user_id}) do
|
||||
|
|
@ -73,4 +121,10 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
|> Repo.all()
|
||||
|> Repo.preload(:app)
|
||||
end
|
||||
|
||||
def is_expired?(%__MODULE__{valid_until: valid_until}) do
|
||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0
|
||||
end
|
||||
|
||||
def is_expired?(_), do: false
|
||||
end
|
||||
|
|
|
|||
54
lib/pleroma/web/oauth/token/strategy/refresh_token.ex
Normal file
54
lib/pleroma/web/oauth/token/strategy/refresh_token.ex
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
|
||||
@moduledoc """
|
||||
Functions for dealing with refresh token strategy.
|
||||
"""
|
||||
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.Token.Strategy.Revoke
|
||||
|
||||
@doc """
|
||||
Will grant access token by refresh token.
|
||||
"""
|
||||
@spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()}
|
||||
def grant(token) do
|
||||
access_token = Repo.preload(token, [:user, :app])
|
||||
|
||||
result =
|
||||
Repo.transaction(fn ->
|
||||
token_params = %{
|
||||
app: access_token.app,
|
||||
user: access_token.user,
|
||||
scopes: access_token.scopes
|
||||
}
|
||||
|
||||
access_token
|
||||
|> revoke_access_token()
|
||||
|> create_access_token(token_params)
|
||||
end)
|
||||
|
||||
case result do
|
||||
{:ok, {:error, reason}} -> {:error, reason}
|
||||
{:ok, {:ok, token}} -> {:ok, token}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
defp revoke_access_token(token) do
|
||||
Revoke.revoke(token)
|
||||
end
|
||||
|
||||
defp create_access_token({:error, error}, _), do: {:error, error}
|
||||
|
||||
defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
|
||||
Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
|
||||
end
|
||||
|
||||
defp add_refresh_token(params, token) do
|
||||
case Config.get([:oauth2, :issue_new_refresh_token], false) do
|
||||
true -> Map.put(params, :refresh_token, token)
|
||||
false -> params
|
||||
end
|
||||
end
|
||||
end
|
||||
22
lib/pleroma/web/oauth/token/strategy/revoke.ex
Normal file
22
lib/pleroma/web/oauth/token/strategy/revoke.ex
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
|
||||
@moduledoc """
|
||||
Functions for dealing with revocation.
|
||||
"""
|
||||
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.OAuth.App
|
||||
alias Pleroma.Web.OAuth.Token
|
||||
|
||||
@doc "Finds and revokes access token for app and by token"
|
||||
@spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()}
|
||||
def revoke(%App{} = app, %{"token" => token} = _attrs) do
|
||||
with {:ok, token} <- Token.get_by_token(app, token),
|
||||
do: revoke(token)
|
||||
end
|
||||
|
||||
@doc "Revokes access token"
|
||||
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
|
||||
def revoke(%Token{} = token) do
|
||||
Repo.delete(token)
|
||||
end
|
||||
end
|
||||
30
lib/pleroma/web/oauth/token/utils.ex
Normal file
30
lib/pleroma/web/oauth/token/utils.ex
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
defmodule Pleroma.Web.OAuth.Token.Utils do
|
||||
@moduledoc """
|
||||
Auxiliary functions for dealing with tokens.
|
||||
"""
|
||||
|
||||
@doc "convert token inserted_at to unix timestamp"
|
||||
def format_created_at(%{inserted_at: inserted_at} = _token) do
|
||||
inserted_at
|
||||
|> DateTime.from_naive!("Etc/UTC")
|
||||
|> DateTime.to_unix()
|
||||
end
|
||||
|
||||
@doc false
|
||||
@spec generate_token(keyword()) :: binary()
|
||||
def generate_token(opts \\ []) do
|
||||
opts
|
||||
|> Keyword.get(:size, 32)
|
||||
|> :crypto.strong_rand_bytes()
|
||||
|> Base.url_encode64(padding: false)
|
||||
end
|
||||
|
||||
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
|
||||
# decoding it. Investigate sometime.
|
||||
def fix_padding(token) do
|
||||
token
|
||||
|> URI.decode()
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|> Base.url_encode64(padding: false)
|
||||
end
|
||||
end
|
||||
|
|
@ -18,15 +18,18 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_in_reply_to(%{"object" => %{"inReplyTo" => in_reply_to}}) do
|
||||
[
|
||||
{:"thr:in-reply-to",
|
||||
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
|
||||
]
|
||||
defp get_in_reply_to(activity) do
|
||||
with %Object{data: %{"inReplyTo" => in_reply_to}} <- Object.normalize(activity) do
|
||||
[
|
||||
{:"thr:in-reply-to",
|
||||
[ref: to_charlist(in_reply_to), href: to_charlist(get_href(in_reply_to))], []}
|
||||
]
|
||||
else
|
||||
_ ->
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp get_in_reply_to(_), do: []
|
||||
|
||||
defp get_mentions(to) do
|
||||
Enum.map(to, fn id ->
|
||||
cond do
|
||||
|
|
@ -98,7 +101,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
[]}
|
||||
end)
|
||||
|
||||
in_reply_to = get_in_reply_to(activity.data)
|
||||
in_reply_to = get_in_reply_to(activity)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
|
|
@ -146,7 +149,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
||||
_in_reply_to = get_in_reply_to(activity.data)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
mentions = activity.recipients |> get_mentions
|
||||
|
||||
|
|
@ -177,7 +179,6 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
|||
updated_at = activity.data["published"]
|
||||
inserted_at = activity.data["published"]
|
||||
|
||||
_in_reply_to = get_in_reply_to(activity.data)
|
||||
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
|
||||
|
||||
retweeted_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.OStatus.DeleteHandler
|
||||
alias Pleroma.Web.OStatus.FollowHandler
|
||||
alias Pleroma.Web.OStatus.NoteHandler
|
||||
|
|
@ -30,7 +31,7 @@ defmodule Pleroma.Web.OStatus do
|
|||
is_nil(object) ->
|
||||
false
|
||||
|
||||
object.data["type"] == "Note" ->
|
||||
Visibility.is_public?(activity) && object.data["type"] == "Note" ->
|
||||
true
|
||||
|
||||
true ->
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ defmodule Pleroma.Web.Push.Impl do
|
|||
@doc "Performs sending notifications for user subscriptions"
|
||||
@spec perform(Notification.t()) :: list(any) | :error
|
||||
def perform(
|
||||
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
|
||||
notif
|
||||
%{
|
||||
activity: %{data: %{"type" => activity_type}, id: activity_id} = activity,
|
||||
user_id: user_id
|
||||
} = notif
|
||||
)
|
||||
when activity_type in @types do
|
||||
actor = User.get_cached_by_ap_id(notif.activity.data["actor"])
|
||||
|
|
@ -30,13 +32,14 @@ defmodule Pleroma.Web.Push.Impl do
|
|||
type = Activity.mastodon_notification_type(notif.activity)
|
||||
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
|
||||
avatar_url = User.avatar_url(actor)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
for subscription <- fetch_subsriptions(user_id),
|
||||
get_in(subscription.data, ["alerts", type]) do
|
||||
%{
|
||||
title: format_title(notif),
|
||||
access_token: subscription.token.token,
|
||||
body: format_body(notif, actor),
|
||||
body: format_body(notif, actor, object),
|
||||
notification_id: notif.id,
|
||||
notification_type: type,
|
||||
icon: avatar_url,
|
||||
|
|
@ -95,25 +98,25 @@ defmodule Pleroma.Web.Push.Impl do
|
|||
end
|
||||
|
||||
def format_body(
|
||||
%{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}},
|
||||
actor
|
||||
%{activity: %{data: %{"type" => "Create"}}},
|
||||
actor,
|
||||
%{data: %{"content" => content}}
|
||||
) do
|
||||
"@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"
|
||||
end
|
||||
|
||||
def format_body(
|
||||
%{activity: %{data: %{"type" => "Announce", "object" => activity_id}}},
|
||||
actor
|
||||
%{activity: %{data: %{"type" => "Announce"}}},
|
||||
actor,
|
||||
%{data: %{"content" => content}}
|
||||
) do
|
||||
%Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id)
|
||||
%Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id)
|
||||
|
||||
"@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"
|
||||
end
|
||||
|
||||
def format_body(
|
||||
%{activity: %{data: %{"type" => type}}},
|
||||
actor
|
||||
actor,
|
||||
_object
|
||||
)
|
||||
when type in ["Follow", "Like"] do
|
||||
case type do
|
||||
|
|
|
|||
|
|
@ -146,34 +146,52 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||
pipe_through([:admin_api, :oauth_write])
|
||||
|
||||
post("/user/follow", AdminAPIController, :user_follow)
|
||||
post("/user/unfollow", AdminAPIController, :user_unfollow)
|
||||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
post("/users/follow", AdminAPIController, :user_follow)
|
||||
post("/users/unfollow", AdminAPIController, :user_unfollow)
|
||||
|
||||
# TODO: to be removed at version 1.0
|
||||
delete("/user", AdminAPIController, :user_delete)
|
||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||
post("/user", AdminAPIController, :user_create)
|
||||
|
||||
delete("/users", AdminAPIController, :user_delete)
|
||||
post("/users", AdminAPIController, :user_create)
|
||||
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
|
||||
put("/users/tag", AdminAPIController, :tag_users)
|
||||
delete("/users/tag", AdminAPIController, :untag_users)
|
||||
|
||||
# TODO: to be removed at version 1.0
|
||||
get("/permission_group/:nickname", AdminAPIController, :right_get)
|
||||
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
|
||||
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
|
||||
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
|
||||
|
||||
put("/activation_status/:nickname", AdminAPIController, :set_activation_status)
|
||||
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
|
||||
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
|
||||
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
|
||||
|
||||
delete(
|
||||
"/users/:nickname/permission_group/:permission_group",
|
||||
AdminAPIController,
|
||||
:right_delete
|
||||
)
|
||||
|
||||
put("/users/:nickname/activation_status", AdminAPIController, :set_activation_status)
|
||||
|
||||
post("/relay", AdminAPIController, :relay_follow)
|
||||
delete("/relay", AdminAPIController, :relay_unfollow)
|
||||
|
||||
get("/invite_token", AdminAPIController, :get_invite_token)
|
||||
get("/invites", AdminAPIController, :invites)
|
||||
post("/revoke_invite", AdminAPIController, :revoke_invite)
|
||||
post("/email_invite", AdminAPIController, :email_invite)
|
||||
get("/users/invite_token", AdminAPIController, :get_invite_token)
|
||||
get("/users/invites", AdminAPIController, :invites)
|
||||
post("/users/revoke_invite", AdminAPIController, :revoke_invite)
|
||||
post("/users/email_invite", AdminAPIController, :email_invite)
|
||||
|
||||
# TODO: to be removed at version 1.0
|
||||
get("/password_reset", AdminAPIController, :get_password_reset)
|
||||
|
||||
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
|
||||
|
||||
get("/users", AdminAPIController, :list_users)
|
||||
get("/users/:nickname", AdminAPIController, :user_show)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
|
|
@ -276,6 +294,9 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get("/suggestions", MastodonAPIController, :suggestions)
|
||||
|
||||
get("/conversations", MastodonAPIController, :conversations)
|
||||
post("/conversations/:id/read", MastodonAPIController, :conversation_read)
|
||||
|
||||
get("/endorsements", MastodonAPIController, :empty_array)
|
||||
|
||||
get("/pleroma/flavour", MastodonAPIController, :get_flavour)
|
||||
|
|
@ -364,6 +385,8 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||
pipe_through(:api)
|
||||
|
||||
post("/accounts", MastodonAPIController, :account_register)
|
||||
|
||||
get("/instance", MastodonAPIController, :masto_instance)
|
||||
get("/instance/peers", MastodonAPIController, :peers)
|
||||
post("/apps", MastodonAPIController, :create_app)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.Salmon do
|
||||
@behaviour Pleroma.Web.Federator.Publisher
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
use Bitwise
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||
alias Pleroma.Web.XML
|
||||
|
||||
|
|
@ -180,12 +186,12 @@ defmodule Pleroma.Web.Salmon do
|
|||
end
|
||||
|
||||
@doc "Pushes an activity to remote account."
|
||||
def send_to_user(%{recipient: %{info: %{salmon: salmon}}} = params),
|
||||
do: send_to_user(Map.put(params, :recipient, salmon))
|
||||
def publish_one(%{recipient: %{info: %{salmon: salmon}}} = params),
|
||||
do: publish_one(Map.put(params, :recipient, salmon))
|
||||
|
||||
def send_to_user(%{recipient: url, feed: feed, poster: poster} = params) when is_binary(url) do
|
||||
def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do
|
||||
with {:ok, %{status: code}} when code in 200..299 <-
|
||||
poster.(
|
||||
@httpoison.post(
|
||||
url,
|
||||
feed,
|
||||
[{"Content-Type", "application/magic-envelope+xml"}]
|
||||
|
|
@ -199,11 +205,11 @@ defmodule Pleroma.Web.Salmon do
|
|||
e ->
|
||||
unless params[:unreachable_since], do: Instances.set_reachable(url)
|
||||
Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
|
||||
:error
|
||||
{:error, "Unreachable instance"}
|
||||
end
|
||||
end
|
||||
|
||||
def send_to_user(_), do: :noop
|
||||
def publish_one(_), do: :noop
|
||||
|
||||
@supported_activities [
|
||||
"Create",
|
||||
|
|
@ -214,13 +220,19 @@ defmodule Pleroma.Web.Salmon do
|
|||
"Delete"
|
||||
]
|
||||
|
||||
def is_representable?(%Activity{data: %{"type" => type}} = activity)
|
||||
when type in @supported_activities,
|
||||
do: Visibility.is_public?(activity)
|
||||
|
||||
def is_representable?(_), do: false
|
||||
|
||||
@doc """
|
||||
Publishes an activity to remote accounts
|
||||
"""
|
||||
@spec publish(User.t(), Pleroma.Activity.t(), Pleroma.HTTP.t()) :: none
|
||||
def publish(user, activity, poster \\ &@httpoison.post/3)
|
||||
@spec publish(User.t(), Pleroma.Activity.t()) :: none
|
||||
def publish(user, activity)
|
||||
|
||||
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity, poster)
|
||||
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity)
|
||||
when type in @supported_activities do
|
||||
feed = ActivityRepresenter.to_simple_form(activity, user, true)
|
||||
|
||||
|
|
@ -244,15 +256,29 @@ defmodule Pleroma.Web.Salmon do
|
|||
|> Enum.each(fn remote_user ->
|
||||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end)
|
||||
|
||||
Pleroma.Web.Federator.publish_single_salmon(%{
|
||||
Publisher.enqueue_one(__MODULE__, %{
|
||||
recipient: remote_user,
|
||||
feed: feed,
|
||||
poster: poster,
|
||||
unreachable_since: reachable_urls_metadata[remote_user.info.salmon]
|
||||
})
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def publish(%{id: id}, _, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
|
||||
def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
|
||||
|
||||
def gather_webfinger_links(%User{} = user) do
|
||||
{:ok, _private, public} = keys_from_pem(user.info.keys)
|
||||
magic_key = encode_key(public)
|
||||
|
||||
[
|
||||
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
|
||||
%{
|
||||
"rel" => "magic-public-key",
|
||||
"href" => "data:application/magic-public-key,#{magic_key}"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def gather_nodeinfo_protocol_names, do: []
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Streamer do
|
|||
use GenServer
|
||||
require Logger
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
|
|
@ -71,6 +72,15 @@ defmodule Pleroma.Web.Streamer do
|
|||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "participation", item: participation}, topics) do
|
||||
user_topic = "direct:#{participation.user_id}"
|
||||
Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n")
|
||||
|
||||
push_to_socket(topics, user_topic, participation)
|
||||
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
|
||||
# filter the recipient list if the activity is not public, see #270.
|
||||
recipient_lists =
|
||||
|
|
@ -192,6 +202,19 @@ defmodule Pleroma.Web.Streamer do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def represent_conversation(%Participation{} = participation) do
|
||||
%{
|
||||
event: "conversation",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{
|
||||
participation: participation,
|
||||
user: participation.user
|
||||
})
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
|
|
@ -214,6 +237,12 @@ defmodule Pleroma.Web.Streamer do
|
|||
end)
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Participation{} = participation) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
send(socket.transport_pid, {:text, represent_conversation(participation)})
|
||||
end)
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Activity{
|
||||
data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id}
|
||||
}) do
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
def delete_account(%{assigns: %{user: user}} = conn, params) do
|
||||
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
|
||||
{:ok, user} ->
|
||||
Task.start(fn -> User.delete(user) end)
|
||||
User.delete(user)
|
||||
json(conn, %{status: "success"})
|
||||
|
||||
{:error, msg} ->
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
end
|
||||
end
|
||||
|
||||
def register_user(params) do
|
||||
def register_user(params, opts \\ []) do
|
||||
token = params["token"]
|
||||
|
||||
params = %{
|
||||
|
|
@ -162,13 +162,22 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
# I have no idea how this error handling works
|
||||
{:error, %{error: Jason.encode!(%{captcha: [error]})}}
|
||||
else
|
||||
registrations_open = Pleroma.Config.get([:instance, :registrations_open])
|
||||
registration_process(registrations_open, params, token)
|
||||
registration_process(
|
||||
params,
|
||||
%{
|
||||
registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
|
||||
token: token
|
||||
},
|
||||
opts
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp registration_process(registration_open, params, token)
|
||||
when registration_open == false or is_nil(registration_open) do
|
||||
defp registration_process(params, %{registrations_open: true}, opts) do
|
||||
create_user(params, opts)
|
||||
end
|
||||
|
||||
defp registration_process(params, %{token: token}, opts) do
|
||||
invite =
|
||||
unless is_nil(token) do
|
||||
Repo.get_by(UserInviteToken, %{token: token})
|
||||
|
|
@ -182,19 +191,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
|
||||
invite when valid_invite? ->
|
||||
UserInviteToken.update_usage!(invite)
|
||||
create_user(params)
|
||||
create_user(params, opts)
|
||||
|
||||
_ ->
|
||||
{:error, "Expired token"}
|
||||
end
|
||||
end
|
||||
|
||||
defp registration_process(true, params, _token) do
|
||||
create_user(params)
|
||||
end
|
||||
|
||||
defp create_user(params) do
|
||||
changeset = User.register_changeset(%User{}, params)
|
||||
defp create_user(params, opts) do
|
||||
changeset = User.register_changeset(%User{}, params, opts)
|
||||
|
||||
case User.register(changeset) do
|
||||
{:ok, user} ->
|
||||
|
|
@ -293,7 +298,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
|||
end
|
||||
|
||||
def get_external_profile(for_user, uri) do
|
||||
with %User{} = user <- User.get_or_fetch(uri) do
|
||||
with {:ok, %User{} = user} <- User.get_or_fetch(uri) do
|
||||
{:ok, UserView.render("show.json", %{user: user, for: for_user})}
|
||||
else
|
||||
_e ->
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
|
|
@ -181,6 +182,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> Map.put("blocking_user", user)
|
||||
|> Map.put("user", user)
|
||||
|> Map.put(:visibility, "direct")
|
||||
|> Map.put(:order, :desc)
|
||||
|
||||
activities =
|
||||
ActivityPub.fetch_activities_query([user.ap_id], params)
|
||||
|
|
@ -438,7 +440,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
true <- user.local,
|
||||
true <- user.info.confirmation_pending,
|
||||
true <- user.info.confirmation_token == token,
|
||||
info_change <- User.Info.confirmation_changeset(user.info, :confirmed),
|
||||
info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
|
||||
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
|
||||
{:ok, _} <- User.update_and_set_cache(changeset) do
|
||||
conn
|
||||
|
|
@ -653,7 +655,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|
||||
defp parse_profile_bio(user, params) do
|
||||
if bio = params["description"] do
|
||||
Map.put(params, "bio", User.parse_bio(bio, user))
|
||||
emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
|
||||
|
||||
emojis =
|
||||
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|
||||
|> Enum.dedup()
|
||||
|
||||
user_info =
|
||||
user.info
|
||||
|> Map.put(
|
||||
"emoji",
|
||||
emojis
|
||||
)
|
||||
|
||||
params
|
||||
|> Map.put("bio", User.parse_bio(bio, user))
|
||||
|> Map.put("info", user_info)
|
||||
else
|
||||
params
|
||||
end
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
|||
created_at = activity.data["published"] |> Utils.date_to_asctime()
|
||||
announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
|
||||
|
||||
text = "#{user.nickname} retweeted a status."
|
||||
text = "#{user.nickname} repeated a status."
|
||||
|
||||
retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity}))
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,13 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
{String.trim(name, ":"), url}
|
||||
end)
|
||||
|
||||
emoji = Enum.dedup(emoji ++ user.info.emoji)
|
||||
|
||||
description_html =
|
||||
(user.bio || "")
|
||||
|> HTML.filter_tags(User.html_filter_policy(for_user))
|
||||
|> Formatter.emojify(emoji)
|
||||
|
||||
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
||||
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
||||
fields =
|
||||
|
|
@ -74,58 +81,49 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||
|
||||
data = %{
|
||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||
"description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
|
||||
"favourites_count" => 0,
|
||||
"followers_count" => user_info[:follower_count],
|
||||
"following" => following,
|
||||
"follows_you" => follows_you,
|
||||
"statusnet_blocking" => statusnet_blocking,
|
||||
"friends_count" => user_info[:following_count],
|
||||
"id" => user.id,
|
||||
"name" => user.name || user.nickname,
|
||||
"name_html" =>
|
||||
if(user.name,
|
||||
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||
else: user.nickname
|
||||
),
|
||||
"profile_image_url" => image,
|
||||
"profile_image_url_https" => image,
|
||||
"profile_image_url_profile_size" => image,
|
||||
"profile_image_url_original" => image,
|
||||
"rights" => %{
|
||||
"delete_others_notice" => !!user.info.is_moderator,
|
||||
"admin" => !!user.info.is_admin
|
||||
},
|
||||
"screen_name" => user.nickname,
|
||||
"statuses_count" => user_info[:note_count],
|
||||
"statusnet_profile_url" => user.ap_id,
|
||||
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
|
||||
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
|
||||
"is_local" => user.local,
|
||||
"locked" => user.info.locked,
|
||||
"default_scope" => user.info.default_scope,
|
||||
"no_rich_text" => user.info.no_rich_text,
|
||||
"hide_followers" => user.info.hide_followers,
|
||||
"hide_follows" => user.info.hide_follows,
|
||||
"fields" => fields,
|
||||
|
||||
# Pleroma extension
|
||||
"pleroma" =>
|
||||
%{
|
||||
"confirmation_pending" => user_info.confirmation_pending,
|
||||
"tags" => user.tags
|
||||
}
|
||||
|> maybe_with_activation_status(user, for_user)
|
||||
}
|
||||
|
||||
data =
|
||||
if(user.info.is_admin || user.info.is_moderator,
|
||||
do: maybe_with_role(data, user, for_user),
|
||||
else: data
|
||||
)
|
||||
%{
|
||||
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
|
||||
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||
"description_html" => description_html,
|
||||
"favourites_count" => 0,
|
||||
"followers_count" => user_info[:follower_count],
|
||||
"following" => following,
|
||||
"follows_you" => follows_you,
|
||||
"statusnet_blocking" => statusnet_blocking,
|
||||
"friends_count" => user_info[:following_count],
|
||||
"id" => user.id,
|
||||
"name" => user.name || user.nickname,
|
||||
"name_html" =>
|
||||
if(user.name,
|
||||
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||
else: user.nickname
|
||||
),
|
||||
"profile_image_url" => image,
|
||||
"profile_image_url_https" => image,
|
||||
"profile_image_url_profile_size" => image,
|
||||
"profile_image_url_original" => image,
|
||||
"screen_name" => user.nickname,
|
||||
"statuses_count" => user_info[:note_count],
|
||||
"statusnet_profile_url" => user.ap_id,
|
||||
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
|
||||
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
|
||||
"is_local" => user.local,
|
||||
"locked" => user.info.locked,
|
||||
"hide_followers" => user.info.hide_followers,
|
||||
"hide_follows" => user.info.hide_follows,
|
||||
"fields" => fields,
|
||||
|
||||
# Pleroma extension
|
||||
"pleroma" =>
|
||||
%{
|
||||
"confirmation_pending" => user_info.confirmation_pending,
|
||||
"tags" => user.tags
|
||||
}
|
||||
|> maybe_with_activation_status(user, for_user)
|
||||
}
|
||||
|> maybe_with_user_settings(user, for_user)
|
||||
|> maybe_with_role(user, for_user)
|
||||
|
||||
if assigns[:token] do
|
||||
Map.put(data, "token", token_string(assigns[:token]))
|
||||
|
|
@ -141,15 +139,35 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
defp maybe_with_activation_status(data, _, _), do: data
|
||||
|
||||
defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
|
||||
Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role})
|
||||
Map.merge(data, %{
|
||||
"role" => role(user),
|
||||
"show_role" => user.info.show_role,
|
||||
"rights" => %{
|
||||
"delete_others_notice" => !!user.info.is_moderator,
|
||||
"admin" => !!user.info.is_admin
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
|
||||
Map.merge(data, %{"role" => role(user)})
|
||||
Map.merge(data, %{
|
||||
"role" => role(user),
|
||||
"rights" => %{
|
||||
"delete_others_notice" => !!user.info.is_moderator,
|
||||
"admin" => !!user.info.is_admin
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
defp maybe_with_role(data, _, _), do: data
|
||||
|
||||
defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do
|
||||
data
|
||||
|> Kernel.put_in(["default_scope"], info.default_scope)
|
||||
|> Kernel.put_in(["no_rich_text"], info.no_rich_text)
|
||||
end
|
||||
|
||||
defp maybe_with_user_settings(data, _, _), do: data
|
||||
defp role(%User{info: %{:is_admin => true}}), do: "admin"
|
||||
defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
|
||||
defp role(_), do: "member"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defmodule Pleroma.Web.WebFinger do
|
|||
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.Salmon
|
||||
alias Pleroma.Web.XML
|
||||
alias Pleroma.XmlBuilder
|
||||
|
|
@ -50,70 +50,40 @@ defmodule Pleroma.Web.WebFinger do
|
|||
end
|
||||
end
|
||||
|
||||
defp gather_links(%User{} = user) do
|
||||
[
|
||||
%{
|
||||
"rel" => "http://webfinger.net/rel/profile-page",
|
||||
"type" => "text/html",
|
||||
"href" => user.ap_id
|
||||
}
|
||||
] ++ Publisher.gather_webfinger_links(user)
|
||||
end
|
||||
|
||||
def represent_user(user, "JSON") do
|
||||
{:ok, user} = ensure_keys_present(user)
|
||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)
|
||||
magic_key = Salmon.encode_key(public)
|
||||
|
||||
%{
|
||||
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
|
||||
"aliases" => [user.ap_id],
|
||||
"links" => [
|
||||
%{
|
||||
"rel" => "http://schemas.google.com/g/2010#updates-from",
|
||||
"type" => "application/atom+xml",
|
||||
"href" => OStatus.feed_path(user)
|
||||
},
|
||||
%{
|
||||
"rel" => "http://webfinger.net/rel/profile-page",
|
||||
"type" => "text/html",
|
||||
"href" => user.ap_id
|
||||
},
|
||||
%{"rel" => "salmon", "href" => OStatus.salmon_path(user)},
|
||||
%{
|
||||
"rel" => "magic-public-key",
|
||||
"href" => "data:application/magic-public-key,#{magic_key}"
|
||||
},
|
||||
%{"rel" => "self", "type" => "application/activity+json", "href" => user.ap_id},
|
||||
%{
|
||||
"rel" => "self",
|
||||
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
|
||||
"href" => user.ap_id
|
||||
},
|
||||
%{
|
||||
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template" => OStatus.remote_follow_path()
|
||||
}
|
||||
]
|
||||
"links" => gather_links(user)
|
||||
}
|
||||
end
|
||||
|
||||
def represent_user(user, "XML") do
|
||||
{:ok, user} = ensure_keys_present(user)
|
||||
{:ok, _private, public} = Salmon.keys_from_pem(user.info.keys)
|
||||
magic_key = Salmon.encode_key(public)
|
||||
|
||||
links =
|
||||
gather_links(user)
|
||||
|> Enum.map(fn link -> {:Link, link} end)
|
||||
|
||||
{
|
||||
:XRD,
|
||||
%{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"},
|
||||
[
|
||||
{:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"},
|
||||
{:Alias, user.ap_id},
|
||||
{:Link,
|
||||
%{
|
||||
rel: "http://schemas.google.com/g/2010#updates-from",
|
||||
type: "application/atom+xml",
|
||||
href: OStatus.feed_path(user)
|
||||
}},
|
||||
{:Link,
|
||||
%{rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: user.ap_id}},
|
||||
{:Link, %{rel: "salmon", href: OStatus.salmon_path(user)}},
|
||||
{:Link,
|
||||
%{rel: "magic-public-key", href: "data:application/magic-public-key,#{magic_key}"}},
|
||||
{:Link, %{rel: "self", type: "application/activity+json", href: user.ap_id}},
|
||||
{:Link,
|
||||
%{rel: "http://ostatus.org/schema/1.0/subscribe", template: OStatus.remote_follow_path()}}
|
||||
]
|
||||
{:Alias, user.ap_id}
|
||||
] ++ links
|
||||
}
|
||||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
defmodule Pleroma.Web.Websub do
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.Federator.Publisher
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||
alias Pleroma.Web.Router.Helpers
|
||||
|
|
@ -18,6 +22,8 @@ defmodule Pleroma.Web.Websub do
|
|||
|
||||
import Ecto.Query
|
||||
|
||||
@behaviour Pleroma.Web.Federator.Publisher
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
def verify(subscription, getter \\ &@httpoison.get/3) do
|
||||
|
|
@ -56,6 +62,13 @@ defmodule Pleroma.Web.Websub do
|
|||
"Undo",
|
||||
"Delete"
|
||||
]
|
||||
|
||||
def is_representable?(%Activity{data: %{"type" => type}} = activity)
|
||||
when type in @supported_activities,
|
||||
do: Visibility.is_public?(activity)
|
||||
|
||||
def is_representable?(_), do: false
|
||||
|
||||
def publish(topic, user, %{data: %{"type" => type}} = activity)
|
||||
when type in @supported_activities do
|
||||
response =
|
||||
|
|
@ -88,12 +101,14 @@ defmodule Pleroma.Web.Websub do
|
|||
unreachable_since: reachable_callbacks_metadata[sub.callback]
|
||||
}
|
||||
|
||||
Federator.publish_single_websub(data)
|
||||
Publisher.enqueue_one(__MODULE__, data)
|
||||
end)
|
||||
end
|
||||
|
||||
def publish(_, _, _), do: ""
|
||||
|
||||
def publish(actor, activity), do: publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity)
|
||||
|
||||
def sign(secret, doc) do
|
||||
:crypto.hmac(:sha, secret, to_string(doc)) |> Base.encode16() |> String.downcase()
|
||||
end
|
||||
|
|
@ -299,4 +314,20 @@ defmodule Pleroma.Web.Websub do
|
|||
{:error, response}
|
||||
end
|
||||
end
|
||||
|
||||
def gather_webfinger_links(%User{} = user) do
|
||||
[
|
||||
%{
|
||||
"rel" => "http://schemas.google.com/g/2010#updates-from",
|
||||
"type" => "application/atom+xml",
|
||||
"href" => OStatus.feed_path(user)
|
||||
},
|
||||
%{
|
||||
"rel" => "http://ostatus.org/schema/1.0/subscribe",
|
||||
"template" => OStatus.remote_follow_path()
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def gather_nodeinfo_protocol_names, do: ["ostatus"]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ defmodule Pleroma.XmlBuilder do
|
|||
defp make_open_tag(tag, attributes) do
|
||||
attributes_string =
|
||||
for {attribute, value} <- attributes do
|
||||
value = String.replace(value, "\"", """)
|
||||
"#{attribute}=\"#{value}\""
|
||||
end
|
||||
|> Enum.join(" ")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue