aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/benchmark.ex25
-rw-r--r--lib/mix/tasks/pleroma/user.ex6
-rw-r--r--lib/pleroma/activity.ex7
-rw-r--r--lib/pleroma/bbs/authenticator.ex16
-rw-r--r--lib/pleroma/bbs/handler.ex147
-rw-r--r--lib/pleroma/bookmark.ex60
-rw-r--r--lib/pleroma/formatter.ex15
-rw-r--r--lib/pleroma/html.ex66
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex2
-rw-r--r--lib/pleroma/plugs/oauth_plug.ex10
-rw-r--r--lib/pleroma/repo.ex28
-rw-r--r--lib/pleroma/user.ex104
-rw-r--r--lib/pleroma/user/info.ex9
-rw-r--r--lib/pleroma/user_invite_token.ex2
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex3
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex2
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex4
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex74
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex7
-rw-r--r--lib/pleroma/web/auth/authenticator.ex26
-rw-r--r--lib/pleroma/web/auth/ldap_authenticator.ex39
-rw-r--r--lib/pleroma/web/auth/pleroma_authenticator.ex15
-rw-r--r--lib/pleroma/web/common_api/common_api.ex19
-rw-r--r--lib/pleroma/web/common_api/utils.ex12
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex89
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex58
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex5
-rw-r--r--lib/pleroma/web/media_proxy/media_proxy.ex50
-rw-r--r--lib/pleroma/web/oauth/app.ex1
-rw-r--r--lib/pleroma/web/oauth/authorization.ex8
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex159
-rw-r--r--lib/pleroma/web/oauth/token.ex81
-rw-r--r--lib/pleroma/web/oauth/token/strategy/refresh_token.ex54
-rw-r--r--lib/pleroma/web/oauth/token/strategy/revoke.ex22
-rw-r--r--lib/pleroma/web/oauth/token/utils.ex30
-rw-r--r--lib/pleroma/web/push/impl.ex25
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex2
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex2
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex18
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex124
40 files changed, 1061 insertions, 365 deletions
diff --git a/lib/mix/tasks/benchmark.ex b/lib/mix/tasks/benchmark.ex
new file mode 100644
index 000000000..0fbb4dbb1
--- /dev/null
+++ b/lib/mix/tasks/benchmark.ex
@@ -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
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index b396ff0de..6a83a8c0d 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -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 = %{
@@ -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
_ ->
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index 9c1c804e0..2dcb97159 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -14,6 +14,8 @@ defmodule Pleroma.Activity do
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
@@ -265,6 +267,11 @@ defmodule Pleroma.Activity do
|> Repo.all()
end
+ @spec query_by_actor(actor()) :: Ecto.Query.t()
+ def query_by_actor(actor) do
+ from(a in Activity, where: a.actor == ^actor)
+ end
+
def restrict_deactivated_users(query) do
from(activity in query,
where:
diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex
new file mode 100644
index 000000000..a2c153720
--- /dev/null
+++ b/lib/pleroma/bbs/authenticator.ex
@@ -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
diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex
new file mode 100644
index 000000000..106fe5d18
--- /dev/null
+++ b/lib/pleroma/bbs/handler.ex
@@ -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
diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex
new file mode 100644
index 000000000..7f8fd43b6
--- /dev/null
+++ b/lib/pleroma/bookmark.ex
@@ -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
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index dab8910c1..3d7c36d21 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -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
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 4b42d8c9b..d1da746de 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -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
- ) do
- {:commit, filter_tags(content, scrubbers)}
- end
-
- def ensure_scrubbed_html(
- content,
- scrubbers,
- true = _fake
+ fake,
+ callback
) do
- {:ignore, filter_tags(content, scrubbers)}
+ content =
+ content
+ |> filter_tags(scrubbers)
+ |> callback.()
+
+ if fake do
+ {:ignore, content}
+ else
+ {:commit, content}
+ end
end
defp generate_scrubber_signature(scrubber) when is_atom(scrubber) do
@@ -106,7 +115,14 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
# links
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
- Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
+
+ Meta.allow_tag_with_this_attribute_values("a", "class", [
+ "hashtag",
+ "u-url",
+ "mention",
+ "u-url mention",
+ "mention u-url"
+ ])
Meta.allow_tag_with_this_attribute_values("a", "rel", [
"tag",
@@ -115,12 +131,15 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
"noreferrer"
])
+ Meta.allow_tag_with_these_attributes("a", ["name", "title"])
+
# paragraphs and linebreaks
Meta.allow_tag_with_these_attributes("br", [])
Meta.allow_tag_with_these_attributes("p", [])
# microformats
- Meta.allow_tag_with_these_attributes("span", ["class"])
+ Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
+ Meta.allow_tag_with_these_attributes("span", [])
# allow inline images for custom emoji
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
@@ -132,6 +151,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do
Meta.allow_tag_with_these_attributes("img", [
"width",
"height",
+ "class",
"title",
"alt"
])
@@ -155,7 +175,14 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.strip_comments()
Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes)
- Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"])
+
+ Meta.allow_tag_with_this_attribute_values("a", "class", [
+ "hashtag",
+ "u-url",
+ "mention",
+ "u-url mention",
+ "mention u-url"
+ ])
Meta.allow_tag_with_this_attribute_values("a", "rel", [
"tag",
@@ -164,6 +191,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
"noreferrer"
])
+ Meta.allow_tag_with_these_attributes("a", ["name", "title"])
+
Meta.allow_tag_with_these_attributes("abbr", ["title"])
Meta.allow_tag_with_these_attributes("b", [])
@@ -177,11 +206,13 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("ol", [])
Meta.allow_tag_with_these_attributes("p", [])
Meta.allow_tag_with_these_attributes("pre", [])
- Meta.allow_tag_with_these_attributes("span", ["class"])
Meta.allow_tag_with_these_attributes("strong", [])
Meta.allow_tag_with_these_attributes("u", [])
Meta.allow_tag_with_these_attributes("ul", [])
+ Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])
+ Meta.allow_tag_with_these_attributes("span", [])
+
@allow_inline_images Keyword.get(@markup, :allow_inline_images)
if @allow_inline_images do
@@ -191,6 +222,7 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes("img", [
"width",
"height",
+ "class",
"title",
"alt"
])
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index f701aaaa5..a476f1d49 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -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}"
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index 5888d596a..9d43732eb 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -16,6 +16,16 @@ defmodule Pleroma.Plugs.OAuthPlug do
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+ 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
+ end
+ end
+
def call(conn, _) do
with {:ok, token_str} <- fetch_token_str(conn),
{:ok, user, token_record} <- fetch_user_and_token(token_str) do
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index aa5d427ae..f57e088bc 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -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
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index d103cd809..10ee01b8c 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Pleroma.Activity
- alias Pleroma.Formatter
+ alias Pleroma.Bookmark
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Registration
@@ -53,8 +53,8 @@ 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(:bookmarks, Bookmark)
has_many(:notifications, Notification)
has_many(:registrations, Registration)
embeds_one(:info, Pleroma.User.Info)
@@ -437,7 +437,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
@@ -521,7 +521,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
@@ -557,7 +565,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, "@"),
@@ -567,9 +575,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
@@ -920,7 +928,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
@@ -1188,7 +1196,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
@@ -1204,22 +1217,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
@@ -1233,11 +1247,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
@@ -1247,20 +1261,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
@@ -1302,7 +1316,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
@@ -1354,18 +1368,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]
@@ -1375,9 +1386,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)
@@ -1411,22 +1423,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()
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 7f22a45b5..1b81619ce 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -41,6 +41,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}
@@ -227,14 +228,6 @@ defmodule Pleroma.User.Info 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}
diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex
index 86f0a5486..fadc89891 100644
--- a/lib/pleroma/user_invite_token.ex
+++ b/lib/pleroma/user_invite_token.ex
@@ -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])
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 6bf54d1cc..d06bc64ea 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -168,7 +168,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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)
@@ -180,6 +179,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)
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 0b80566bf..c967ab7a9 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -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)
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index a7a20ca37..93808517b 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -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}
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 52666a409..508f3532f 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -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,22 +436,48 @@ 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
- if not User.locked?(followed) do
+ with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
+ {:user_blocked, false} <-
+ {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
+ {:user_locked, false} <- {:user_locked, User.locked?(followed)},
+ {:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
object: data,
local: true
})
-
- User.follow(follower, followed)
+ else
+ {:user_blocked, true} ->
+ {:ok, _} = Utils.update_follow_state(activity, "reject")
+
+ ActivityPub.reject(%{
+ to: [follower.ap_id],
+ actor: followed,
+ object: data,
+ local: true
+ })
+
+ {:follow, {:error, _}} ->
+ {:ok, _} = Utils.update_follow_state(activity, "reject")
+
+ ActivityPub.reject(%{
+ to: [follower.ap_id],
+ actor: followed,
+ object: data,
+ local: true
+ })
+
+ {:user_locked, true} ->
+ :noop
end
{:ok, activity}
else
- _e -> :error
+ _e ->
+ :error
end
end
@@ -459,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"]),
@@ -485,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"]),
@@ -509,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}
@@ -522,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
@@ -577,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
@@ -596,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}
@@ -614,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}
@@ -633,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}
@@ -647,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)
@@ -666,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}
@@ -830,10 +856,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
@@ -851,6 +883,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
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 5926a3294..1254fdf6c 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -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))
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index b02f595dc..d4e0ffa80 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -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
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index 363c99597..177c05636 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -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}
+ 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
+ {:error, {:ldap_connection_error, _}} ->
+ # When LDAP is unavailable, try 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
- else
- # Fall back to default authenticator
- @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
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index d647f1e05..dd79cdcf7 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -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
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index cfbc5dc10..b53869c75 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -4,6 +4,7 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Activity
+ alias Pleroma.Bookmark
alias Pleroma.Formatter
alias Pleroma.Object
alias Pleroma.ThreadMute
@@ -150,8 +151,8 @@ defmodule Pleroma.Web.CommonAPI do
),
{to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),
context <- make_context(in_reply_to),
- cw <- data["spoiler_text"],
- full_payload <- String.trim(status <> (data["spoiler_text"] || "")),
+ cw <- data["spoiler_text"] || "",
+ full_payload <- String.trim(status <> cw),
length when length in 1..limit <- String.length(full_payload),
object <-
make_note_data(
@@ -169,10 +170,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
res =
ActivityPub.create(
@@ -282,6 +280,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)},
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 887f878c4..1dfe50b40 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -183,6 +183,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
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.
"""
def format_input(text, "text/html", options) do
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 0ba8d9eea..b099199af 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -6,8 +6,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
alias Ecto.Changeset
alias Pleroma.Activity
+ alias Pleroma.Bookmark
alias Pleroma.Config
alias Pleroma.Filter
+ alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Fetcher
@@ -35,7 +37,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
- import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
+ alias Pleroma.Web.ControllerHelper
import Ecto.Query
require Logger
@@ -46,7 +48,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
action_fallback(:errors)
def create_app(conn, params) do
- scopes = oauth_scopes(params, ["read"])
+ scopes = ControllerHelper.oauth_scopes(params, ["read"])
app_attrs =
params
@@ -85,7 +87,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 +97,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 +119,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),
@@ -279,6 +293,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> ActivityPub.contain_timeline(user)
|> Enum.reverse()
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> add_link_headers(:home_timeline, activities)
|> put_view(StatusView)
@@ -297,6 +313,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
|> put_view(StatusView)
@@ -304,7 +322,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_id(params["id"]) do
+ with %User{} = user <- User.get_cached_by_id(params["id"]),
+ reading_user <- Repo.preload(reading_user, :bookmarks) do
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
conn
@@ -331,6 +350,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> ActivityPub.fetch_activities_query(params)
|> Pagination.fetch_paginated(params)
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> add_link_headers(:dm_timeline, activities)
|> put_view(StatusView)
@@ -340,6 +361,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user})
@@ -489,6 +512,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
%Activity{} = announce <- Activity.normalize(announce.data) do
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: announce, for: user, as: :activity})
@@ -498,6 +523,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
@@ -545,10 +572,11 @@ 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
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
@@ -557,10 +585,11 @@ 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
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
@@ -683,7 +712,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 +720,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 +734,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 +791,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 +808,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 +816,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
@@ -1081,6 +1110,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
ActivityPub.fetch_activities([], params)
|> Enum.reverse()
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> add_link_headers(:favourites, activities)
|> put_view(StatusView)
@@ -1124,15 +1155,20 @@ 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)
+ user = Repo.preload(user, bookmarks: :activity)
+
+ 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 -> b.activity end)
conn
+ |> add_link_headers(:bookmarks, bookmarks)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
@@ -1207,7 +1243,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
@@ -1238,6 +1274,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse()
+ user = Repo.preload(user, bookmarks: :activity)
+
conn
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
@@ -1265,8 +1303,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(),
@@ -1623,7 +1660,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
)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index d87fdb15d..779b9a382 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -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 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 with_notification_settings(data, _, _), do: data
+ defp maybe_put_notification_settings(data, _, _), do: data
end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 7dd80d708..62d064d71 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -85,7 +85,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
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 = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity)
mentions =
activity.recipients
@@ -148,7 +149,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 = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity)
attachment_data = object.data["attachment"] || []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex
index 3bd2affe9..5762e767b 100644
--- a/lib/pleroma/web/media_proxy/media_proxy.ex
+++ b/lib/pleroma/web/media_proxy/media_proxy.ex
@@ -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)
diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex
index 3476da484..bccc2ac96 100644
--- a/lib/pleroma/web/oauth/app.ex
+++ b/lib/pleroma/web/oauth/app.ex
@@ -6,6 +6,7 @@ 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)
diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex
index 3461f9983..ca3901cc4 100644
--- a/lib/pleroma/web/oauth/authorization.ex
+++ b/lib/pleroma/web/oauth/authorization.ex
@@ -13,6 +13,7 @@ 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: [])
@@ -63,4 +64,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
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 688eaca11..e3c01217d 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -13,11 +13,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
+ alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
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)
@@ -138,25 +142,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
- }
-
- json(conn, response)
+ {:ok, token} <- Token.exchange_token(app, auth) 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)
@@ -177,16 +189,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
true <- Enum.any?(scopes),
{: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 +221,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do
token_exchange(conn, params)
end
- def token_revoke(conn, %{"token" => token} = params) do
+ # 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),
- %Token{} = token <- Repo.get_by(Token, token: token, app_id: app.id),
- {:ok, %Token{}} <- Repo.delete(token) do
+ {:ok, _token} <- RevokeToken.revoke(app, params) do
json(conn, %{})
else
_error ->
@@ -230,6 +235,15 @@ 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 =
@@ -278,25 +292,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
_ ->
@@ -399,36 +410,30 @@ defmodule Pleroma.Web.OAuth.OAuthController do
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 +446,16 @@ 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
end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
index 399140003..4e5d1d118 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/oauth/token.ex
@@ -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,67 @@ 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
+
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)
+ create_token(
+ app,
+ User.get_cached_by_id(auth.user_id),
+ %{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)
-
- 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)
- }
-
- Repo.insert(token)
+ defp put_token(changeset) do
+ changeset
+ |> change(%{token: Token.Utils.generate_token()})
+ |> validate_required([:token])
+ |> unique_constraint(:token)
+ end
+
+ defp put_refresh_token(changeset, attrs) do
+ refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_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
+
+ 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, :user_id, :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 +116,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
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
new file mode 100644
index 000000000..7df0be14e
--- /dev/null
+++ b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
@@ -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
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex
new file mode 100644
index 000000000..dea63ca54
--- /dev/null
+++ b/lib/pleroma/web/oauth/token/strategy/revoke.ex
@@ -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
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex
new file mode 100644
index 000000000..a81560a1c
--- /dev/null
+++ b/lib/pleroma/web/oauth/token/utils.ex
@@ -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
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 2233480c5..35d3ff07c 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -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
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 6c8c2fe24..7b7fd912b 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -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} ->
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 2353a95a8..1e48b0b39 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -296,7 +296,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 ->
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 79ed9dad2..ef7b6fe65 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -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
@@ -653,7 +654,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
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 0791ed760..f0a4ddbd3 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -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"