aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/activity.ex2
-rw-r--r--lib/pleroma/application.ex26
-rw-r--r--lib/pleroma/config.ex4
-rw-r--r--lib/pleroma/emoji.ex65
-rw-r--r--lib/pleroma/formatter.ex8
-rw-r--r--lib/pleroma/html.ex15
-rw-r--r--lib/pleroma/notification.ex63
-rw-r--r--lib/pleroma/registration.ex57
-rw-r--r--lib/pleroma/repo.ex4
-rw-r--r--lib/pleroma/scheduled_activity.ex161
-rw-r--r--lib/pleroma/scheduled_activity_worker.ex58
-rw-r--r--lib/pleroma/user.ex28
-rw-r--r--lib/pleroma/user/info.ex17
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex42
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex5
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex20
-rw-r--r--lib/pleroma/web/auth/authenticator.ex26
-rw-r--r--lib/pleroma/web/auth/ldap_authenticator.ex15
-rw-r--r--lib/pleroma/web/auth/pleroma_authenticator.ex66
-rw-r--r--lib/pleroma/web/common_api/common_api.ex2
-rw-r--r--lib/pleroma/web/common_api/utils.ex2
-rw-r--r--lib/pleroma/web/endpoint.ex38
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api.ex7
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex118
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex22
-rw-r--r--lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex57
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex39
-rw-r--r--lib/pleroma/web/metadata/utils.ex2
-rw-r--r--lib/pleroma/web/oauth/fallback_controller.ex17
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex299
-rw-r--r--lib/pleroma/web/push/impl.ex6
-rw-r--r--lib/pleroma/web/push/push.ex48
-rw-r--r--lib/pleroma/web/router.ex26
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex13
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex13
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/register.html.eex43
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/show.html.eex22
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex15
-rw-r--r--lib/pleroma/web/twitter_api/views/activity_view.ex6
39 files changed, 1266 insertions, 211 deletions
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index bc3f8caba..ab8861b27 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -31,7 +31,7 @@ defmodule Pleroma.Activity do
field(:data, :map)
field(:local, :boolean, default: true)
field(:actor, :string)
- field(:recipients, {:array, :string})
+ field(:recipients, {:array, :string}, default: [])
has_many(:notifications, Notification, on_delete: :delete_all)
# Attention: this is a fake relation, don't try to preload it blindly and expect it to work!
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 782d1d589..eeb415084 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -25,6 +25,7 @@ defmodule Pleroma.Application do
import Cachex.Spec
Pleroma.Config.DeprecationWarnings.warn()
+ setup_instrumenters()
# Define workers and child supervisors to be supervised
children =
@@ -103,14 +104,15 @@ defmodule Pleroma.Application do
],
id: :cachex_idem
),
- worker(Pleroma.FlakeId, [])
+ worker(Pleroma.FlakeId, []),
+ worker(Pleroma.ScheduledActivityWorker, [])
] ++
hackney_pool_children() ++
[
worker(Pleroma.Web.Federator.RetryQueue, []),
worker(Pleroma.Stats, []),
- worker(Pleroma.Web.Push, []),
- worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary)
+ worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),
+ worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init)
] ++
streamer_child() ++
chat_child() ++
@@ -126,6 +128,24 @@ defmodule Pleroma.Application do
Supervisor.start_link(children, opts)
end
+ defp setup_instrumenters do
+ require Prometheus.Registry
+
+ :ok =
+ :telemetry.attach(
+ "prometheus-ecto",
+ [:pleroma, :repo, :query],
+ &Pleroma.Repo.Instrumenter.handle_event/4,
+ %{}
+ )
+
+ Prometheus.Registry.register_collector(:prometheus_process_collector)
+ Pleroma.Web.Endpoint.MetricsExporter.setup()
+ Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
+ Pleroma.Web.Endpoint.Instrumenter.setup()
+ Pleroma.Repo.Instrumenter.setup()
+ end
+
def enabled_hackney_pools do
[:media] ++
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do
diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex
index 21507cd38..189faa15f 100644
--- a/lib/pleroma/config.ex
+++ b/lib/pleroma/config.ex
@@ -57,4 +57,8 @@ defmodule Pleroma.Config do
def delete(key) do
Application.delete_env(:pleroma, key)
end
+
+ def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])
+
+ def oauth_consumer_enabled?, do: oauth_consumer_strategies() != []
end
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index f3f08cd9d..87c7f2cec 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -8,13 +8,19 @@ defmodule Pleroma.Emoji do
* the built-in Finmojis (if enabled in configuration),
* the files: `config/emoji.txt` and `config/custom_emoji.txt`
- * glob paths
+ * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder
This GenServer stores in an ETS table the list of the loaded emojis, and also allows to reload the list at runtime.
"""
use GenServer
+
+ @type pattern :: Regex.t() | module() | String.t()
+ @type patterns :: pattern() | [pattern()]
+ @type group_patterns :: keyword(patterns())
+
@ets __MODULE__.Ets
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
+ @groups Application.get_env(:pleroma, :emoji)[:groups]
@doc false
def start_link do
@@ -73,13 +79,14 @@ defmodule Pleroma.Emoji do
end
defp load do
+ finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)
+ shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || []
+
emojis =
- (load_finmoji(Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled)) ++
+ (load_finmoji(finmoji_enabled) ++
load_from_file("config/emoji.txt") ++
load_from_file("config/custom_emoji.txt") ++
- load_from_globs(
- Keyword.get(Application.get_env(:pleroma, :emoji, []), :shortcode_globs, [])
- ))
+ load_from_globs(shortcode_globs))
|> Enum.reject(fn value -> value == nil end)
true = :ets.insert(@ets, emojis)
@@ -151,9 +158,12 @@ defmodule Pleroma.Emoji do
"white_nights",
"woollysocks"
]
+
defp load_finmoji(true) do
Enum.map(@finmoji, fn finmoji ->
- {finmoji, "/finmoji/128px/#{finmoji}-128.png"}
+ file_name = "/finmoji/128px/#{finmoji}-128.png"
+ group = match_extra(@groups, file_name)
+ {finmoji, file_name, to_string(group)}
end)
end
@@ -172,8 +182,14 @@ defmodule Pleroma.Emoji do
|> Stream.map(&String.trim/1)
|> Stream.map(fn line ->
case String.split(line, ~r/,\s*/) do
- [name, file] -> {name, file}
- _ -> nil
+ [name, file, tags] ->
+ {name, file, tags}
+
+ [name, file] ->
+ {name, file, to_string(match_extra(@groups, file))}
+
+ _ ->
+ nil
end
end)
|> Enum.to_list()
@@ -190,9 +206,40 @@ defmodule Pleroma.Emoji do
|> Enum.concat()
Enum.map(paths, fn path ->
+ tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
shortcode = Path.basename(path, Path.extname(path))
external_path = Path.join("/", Path.relative_to(path, static_path))
- {shortcode, external_path}
+ {shortcode, external_path, to_string(tag)}
+ end)
+ end
+
+ @doc """
+ Finds a matching group for the given emoji filename
+ """
+ @spec match_extra(group_patterns(), String.t()) :: atom() | nil
+ def match_extra(group_patterns, filename) do
+ match_group_patterns(group_patterns, fn pattern ->
+ case pattern do
+ %Regex{} = regex -> Regex.match?(regex, filename)
+ string when is_binary(string) -> filename == string
+ end
+ end)
+ end
+
+ defp match_group_patterns(group_patterns, matcher) do
+ Enum.find_value(group_patterns, fn {group, patterns} ->
+ patterns =
+ patterns
+ |> List.wrap()
+ |> Enum.map(fn pattern ->
+ if String.contains?(pattern, "*") do
+ ~r(#{String.replace(pattern, "*", ".*")})
+ else
+ pattern
+ end
+ end)
+
+ Enum.any?(patterns, matcher) && group
end)
end
end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index e3625383b..8ea9dbd38 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -77,9 +77,9 @@ defmodule Pleroma.Formatter do
def emojify(text, nil), do: text
def emojify(text, emoji, strip \\ false) do
- Enum.reduce(emoji, text, fn {emoji, file}, text ->
- emoji = HTML.strip_tags(emoji)
- file = HTML.strip_tags(file)
+ Enum.reduce(emoji, text, fn emoji_data, text ->
+ emoji = HTML.strip_tags(elem(emoji_data, 0))
+ file = HTML.strip_tags(elem(emoji_data, 1))
html =
if not strip do
@@ -101,7 +101,7 @@ defmodule Pleroma.Formatter do
def demojify(text, nil), do: text
def get_emoji(text) when is_binary(text) do
- Enum.filter(Emoji.get_all(), fn {emoji, _} -> String.contains?(text, ":#{emoji}:") end)
+ Enum.filter(Emoji.get_all(), fn {emoji, _, _} -> String.contains?(text, ":#{emoji}:") end)
end
def get_emoji(_), do: []
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index 1e48749a8..7f1dbe28c 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -28,21 +28,20 @@ defmodule Pleroma.HTML do
def filter_tags(html), do: filter_tags(html, nil)
def strip_tags(html), do: Scrubber.scrub(html, Scrubber.StripTags)
- # TODO: rename object to activity because that's what it is really working with
- def get_cached_scrubbed_html_for_object(content, scrubbers, object, module) do
- key = "#{module}#{generate_scrubber_signature(scrubbers)}|#{object.id}"
+ def get_cached_scrubbed_html_for_activity(content, scrubbers, activity, key \\ "") do
+ key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"
Cachex.fetch!(:scrubber_cache, key, fn _key ->
- ensure_scrubbed_html(content, scrubbers, object.data["object"]["fake"] || false)
+ ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false)
end)
end
- def get_cached_stripped_html_for_object(content, object, module) do
- get_cached_scrubbed_html_for_object(
+ def get_cached_stripped_html_for_activity(content, activity, key) do
+ get_cached_scrubbed_html_for_activity(
content,
HtmlSanitizeEx.Scrubber.StripTags,
- object,
- module
+ activity,
+ key
)
end
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 7c5856438..15789907a 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -122,13 +122,7 @@ defmodule Pleroma.Notification do
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do
- unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
- CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
- (activity.data["type"] == "Follow" and
- Enum.any?(Notification.for_user(user), fn notif ->
- notif.activity.data["type"] == "Follow" and
- notif.activity.data["actor"] == activity.data["actor"]
- end)) do
+ unless skip?(activity, user) do
notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification)
Pleroma.Web.Streamer.stream("user", notification)
@@ -155,4 +149,59 @@ defmodule Pleroma.Notification do
end
def get_notified_from_activity(_, _local_only), do: []
+
+ def skip?(activity, user) do
+ [:self, :blocked, :local, :muted, :followers, :follows, :recently_followed]
+ |> Enum.any?(&skip?(&1, activity, user))
+ end
+
+ def skip?(:self, activity, user) do
+ activity.data["actor"] == user.ap_id
+ end
+
+ def skip?(:blocked, activity, user) do
+ actor = activity.data["actor"]
+ User.blocks?(user, %{ap_id: actor})
+ end
+
+ def skip?(:local, %{local: true}, %{info: %{notification_settings: %{"local" => false}}}),
+ do: true
+
+ def skip?(:local, %{local: false}, %{info: %{notification_settings: %{"remote" => false}}}),
+ do: true
+
+ def skip?(:muted, activity, user) do
+ actor = activity.data["actor"]
+
+ User.mutes?(user, %{ap_id: actor}) or
+ CommonAPI.thread_muted?(user, activity)
+ end
+
+ def skip?(
+ :followers,
+ activity,
+ %{info: %{notification_settings: %{"followers" => false}}} = user
+ ) do
+ actor = activity.data["actor"]
+ follower = User.get_cached_by_ap_id(actor)
+ User.following?(follower, user)
+ end
+
+ def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
+ actor = activity.data["actor"]
+ followed = User.get_by_ap_id(actor)
+ User.following?(user, followed)
+ end
+
+ def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
+ actor = activity.data["actor"]
+
+ Notification.for_user(user)
+ |> Enum.any?(fn
+ %{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
+ _ -> false
+ end)
+ end
+
+ def skip?(_, _, _), do: false
end
diff --git a/lib/pleroma/registration.ex b/lib/pleroma/registration.ex
new file mode 100644
index 000000000..21fd1fc3f
--- /dev/null
+++ b/lib/pleroma/registration.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Registration do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias Pleroma.Registration
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @primary_key {:id, Pleroma.FlakeId, autogenerate: true}
+
+ schema "registrations" do
+ belongs_to(:user, User, type: Pleroma.FlakeId)
+ field(:provider, :string)
+ field(:uid, :string)
+ field(:info, :map, default: %{})
+
+ timestamps()
+ end
+
+ def nickname(registration, default \\ nil),
+ do: Map.get(registration.info, "nickname", default)
+
+ def email(registration, default \\ nil),
+ do: Map.get(registration.info, "email", default)
+
+ def name(registration, default \\ nil),
+ do: Map.get(registration.info, "name", default)
+
+ def description(registration, default \\ nil),
+ do: Map.get(registration.info, "description", default)
+
+ def changeset(registration, params \\ %{}) do
+ registration
+ |> cast(params, [:user_id, :provider, :uid, :info])
+ |> validate_required([:provider, :uid])
+ |> foreign_key_constraint(:user_id)
+ |> unique_constraint(:uid, name: :registrations_provider_uid_index)
+ end
+
+ def bind_to_user(registration, user) do
+ registration
+ |> changeset(%{user_id: (user && user.id) || nil})
+ |> Repo.update()
+ end
+
+ def get_by_provider_uid(provider, uid) do
+ Repo.get_by(Registration,
+ provider: to_string(provider),
+ uid: to_string(uid)
+ )
+ end
+end
diff --git a/lib/pleroma/repo.ex b/lib/pleroma/repo.ex
index 4af1bde56..aa5d427ae 100644
--- a/lib/pleroma/repo.ex
+++ b/lib/pleroma/repo.ex
@@ -8,6 +8,10 @@ defmodule Pleroma.Repo do
adapter: Ecto.Adapters.Postgres,
migration_timestamps: [type: :naive_datetime_usec]
+ defmodule Instrumenter do
+ use Prometheus.EctoInstrumenter
+ end
+
@doc """
Dynamically loads the repository url from the
DATABASE_URL environment variable.
diff --git a/lib/pleroma/scheduled_activity.ex b/lib/pleroma/scheduled_activity.ex
new file mode 100644
index 000000000..de0e54699
--- /dev/null
+++ b/lib/pleroma/scheduled_activity.ex
@@ -0,0 +1,161 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ScheduledActivity do
+ use Ecto.Schema
+
+ alias Pleroma.Config
+ alias Pleroma.Repo
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI.Utils
+
+ import Ecto.Query
+ import Ecto.Changeset
+
+ @min_offset :timer.minutes(5)
+
+ schema "scheduled_activities" do
+ belongs_to(:user, User, type: Pleroma.FlakeId)
+ field(:scheduled_at, :naive_datetime)
+ field(:params, :map)
+
+ timestamps()
+ end
+
+ def changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
+ scheduled_activity
+ |> cast(attrs, [:scheduled_at, :params])
+ |> validate_required([:scheduled_at, :params])
+ |> validate_scheduled_at()
+ |> with_media_attachments()
+ end
+
+ defp with_media_attachments(
+ %{changes: %{params: %{"media_ids" => media_ids} = params}} = changeset
+ )
+ when is_list(media_ids) do
+ media_attachments = Utils.attachments_from_ids(%{"media_ids" => media_ids})
+
+ params =
+ params
+ |> Map.put("media_attachments", media_attachments)
+ |> Map.put("media_ids", media_ids)
+
+ put_change(changeset, :params, params)
+ end
+
+ defp with_media_attachments(changeset), do: changeset
+
+ def update_changeset(%ScheduledActivity{} = scheduled_activity, attrs) do
+ scheduled_activity
+ |> cast(attrs, [:scheduled_at])
+ |> validate_required([:scheduled_at])
+ |> validate_scheduled_at()
+ end
+
+ def validate_scheduled_at(changeset) do
+ validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
+ cond do
+ not far_enough?(scheduled_at) ->
+ [scheduled_at: "must be at least 5 minutes from now"]
+
+ exceeds_daily_user_limit?(changeset.data.user_id, scheduled_at) ->
+ [scheduled_at: "daily limit exceeded"]
+
+ exceeds_total_user_limit?(changeset.data.user_id) ->
+ [scheduled_at: "total limit exceeded"]
+
+ true ->
+ []
+ end
+ end)
+ end
+
+ def exceeds_daily_user_limit?(user_id, scheduled_at) do
+ ScheduledActivity
+ |> where(user_id: ^user_id)
+ |> where([sa], type(sa.scheduled_at, :date) == type(^scheduled_at, :date))
+ |> select([sa], count(sa.id))
+ |> Repo.one()
+ |> Kernel.>=(Config.get([ScheduledActivity, :daily_user_limit]))
+ end
+
+ def exceeds_total_user_limit?(user_id) do
+ ScheduledActivity
+ |> where(user_id: ^user_id)
+ |> select([sa], count(sa.id))
+ |> Repo.one()
+ |> Kernel.>=(Config.get([ScheduledActivity, :total_user_limit]))
+ end
+
+ def far_enough?(scheduled_at) when is_binary(scheduled_at) do
+ with {:ok, scheduled_at} <- Ecto.Type.cast(:naive_datetime, scheduled_at) do
+ far_enough?(scheduled_at)
+ else
+ _ -> false
+ end
+ end
+
+ def far_enough?(scheduled_at) do
+ now = NaiveDateTime.utc_now()
+ diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
+ diff > @min_offset
+ end
+
+ def new(%User{} = user, attrs) do
+ %ScheduledActivity{user_id: user.id}
+ |> changeset(attrs)
+ end
+
+ def create(%User{} = user, attrs) do
+ user
+ |> new(attrs)
+ |> Repo.insert()
+ end
+
+ def get(%User{} = user, scheduled_activity_id) do
+ ScheduledActivity
+ |> where(user_id: ^user.id)
+ |> where(id: ^scheduled_activity_id)
+ |> Repo.one()
+ end
+
+ def update(%ScheduledActivity{} = scheduled_activity, attrs) do
+ scheduled_activity
+ |> update_changeset(attrs)
+ |> Repo.update()
+ end
+
+ def delete(%ScheduledActivity{} = scheduled_activity) do
+ scheduled_activity
+ |> Repo.delete()
+ end
+
+ def delete(id) when is_binary(id) or is_integer(id) do
+ ScheduledActivity
+ |> where(id: ^id)
+ |> select([sa], sa)
+ |> Repo.delete_all()
+ |> case do
+ {1, [scheduled_activity]} -> {:ok, scheduled_activity}
+ _ -> :error
+ end
+ end
+
+ def for_user_query(%User{} = user) do
+ ScheduledActivity
+ |> where(user_id: ^user.id)
+ end
+
+ def due_activities(offset \\ 0) do
+ naive_datetime =
+ NaiveDateTime.utc_now()
+ |> NaiveDateTime.add(offset, :millisecond)
+
+ ScheduledActivity
+ |> where([sa], sa.scheduled_at < ^naive_datetime)
+ |> Repo.all()
+ end
+end
diff --git a/lib/pleroma/scheduled_activity_worker.ex b/lib/pleroma/scheduled_activity_worker.ex
new file mode 100644
index 000000000..65b38622f
--- /dev/null
+++ b/lib/pleroma/scheduled_activity_worker.ex
@@ -0,0 +1,58 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ScheduledActivityWorker do
+ @moduledoc """
+ Sends scheduled activities to the job queue.
+ """
+
+ alias Pleroma.Config
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+ use GenServer
+ require Logger
+
+ @schedule_interval :timer.minutes(1)
+
+ def start_link do
+ GenServer.start_link(__MODULE__, nil)
+ end
+
+ def init(_) do
+ if Config.get([ScheduledActivity, :enabled]) do
+ schedule_next()
+ {:ok, nil}
+ else
+ :ignore
+ end
+ end
+
+ def perform(:execute, scheduled_activity_id) do
+ try do
+ {:ok, scheduled_activity} = ScheduledActivity.delete(scheduled_activity_id)
+ %User{} = user = User.get_cached_by_id(scheduled_activity.user_id)
+ {:ok, _result} = CommonAPI.post(user, scheduled_activity.params)
+ rescue
+ error ->
+ Logger.error(
+ "#{__MODULE__} Couldn't create a status from the scheduled activity: #{inspect(error)}"
+ )
+ end
+ end
+
+ def handle_info(:perform, state) do
+ ScheduledActivity.due_activities(@schedule_interval)
+ |> Enum.each(fn scheduled_activity ->
+ PleromaJobQueue.enqueue(:scheduled_activities, __MODULE__, [:execute, scheduled_activity.id])
+ end)
+
+ schedule_next()
+ {:noreply, state}
+ end
+
+ defp schedule_next do
+ Process.send_after(self(), :perform, @schedule_interval)
+ end
+end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 8a0243604..6e2269aff 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -13,6 +13,7 @@ defmodule Pleroma.User do
alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
+ alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
@@ -55,6 +56,7 @@ defmodule Pleroma.User do
field(:bookmarks, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec)
has_many(:notifications, Notification)
+ has_many(:registrations, Registration)
embeds_one(:info, Pleroma.User.Info)
timestamps()
@@ -216,7 +218,7 @@ defmodule Pleroma.User do
changeset =
struct
|> cast(params, [:bio, :email, :name, :nickname, :password, :password_confirmation])
- |> validate_required([:email, :name, :nickname, :password, :password_confirmation])
+ |> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> unique_constraint(:nickname)
@@ -227,6 +229,13 @@ defmodule Pleroma.User do
|> validate_length(:name, min: 1, max: 100)
|> put_change(:info, info_change)
+ changeset =
+ if opts[:external] do
+ changeset
+ else
+ validate_required(changeset, [:email])
+ end
+
if changeset.valid? do
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
@@ -505,11 +514,10 @@ defmodule Pleroma.User do
end
end
+ def get_by_email(email), do: Repo.get_by(User, email: email)
+
def get_by_nickname_or_email(nickname_or_email) do
- case user = Repo.get_by(User, nickname: nickname_or_email) do
- %User{} -> user
- nil -> Repo.get_by(User, email: nickname_or_email)
- end
+ get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
end
def get_cached_user_info(user) do
@@ -977,6 +985,8 @@ defmodule Pleroma.User do
unfollow(blocked, blocker)
end
+ {:ok, blocker} = update_follower_count(blocker)
+
info_cng =
blocker.info
|> User.Info.add_to_block(ap_id)
@@ -1131,6 +1141,14 @@ defmodule Pleroma.User do
update_and_set_cache(cng)
end
+ def update_notification_settings(%User{} = user, settings \\ %{}) do
+ info_changeset = User.Info.update_notification_settings(user.info, settings)
+
+ change(user)
+ |> put_embed(:info, info_changeset)
+ |> update_and_set_cache()
+ end
+
def delete(%User{} = user) do
{:ok, user} = User.deactivate(user)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 1cf46feb3..5afa7988c 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -41,6 +41,10 @@ defmodule Pleroma.User.Info do
field(:pinned_activities, {:array, :string}, default: [])
field(:flavour, :string, default: nil)
+ field(:notification_settings, :map,
+ default: %{"remote" => true, "local" => true, "followers" => true, "follows" => true}
+ )
+
# Found in the wild
# ap_id -> Where is this used?
# bio -> Where is this used?
@@ -58,6 +62,19 @@ defmodule Pleroma.User.Info do
|> validate_required([:deactivated])
end
+ def update_notification_settings(info, settings) do
+ notification_settings =
+ info.notification_settings
+ |> Map.merge(settings)
+ |> Map.take(["remote", "local", "followers", "follows"])
+
+ params = %{notification_settings: notification_settings}
+
+ info
+ |> cast(params, [:notification_settings])
+ |> validate_required([:notification_settings])
+ end
+
def add_to_note_count(info, number) do
set_note_count(info, info.note_count + number)
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index f733ae7e1..49ea73204 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -83,6 +83,22 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_content_map
|> fix_likes
|> fix_addressing
+ |> fix_summary
+ end
+
+ def fix_summary(%{"summary" => nil} = object) do
+ object
+ |> Map.put("summary", "")
+ end
+
+ def fix_summary(%{"summary" => _} = object) do
+ # summary is present, nothing to do
+ object
+ end
+
+ def fix_summary(object) do
+ object
+ |> Map.put("summary", "")
end
def fix_addressing_list(map, field) do
@@ -954,7 +970,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
defp strip_internal_tags(object), do: object
- defp user_upgrade_task(user) do
+ def perform(:user_upgrade, user) do
# we pass a fake user so that the followers collection is stripped away
old_follower_address = User.ap_followers(%User{nickname: user.nickname})
@@ -999,28 +1015,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
Repo.update_all(q, [])
end
- def upgrade_user_from_ap_id(ap_id, async \\ true) do
+ def upgrade_user_from_ap_id(ap_id) do
with %User{local: false} = user <- User.get_by_ap_id(ap_id),
- {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id) do
- already_ap = User.ap_enabled?(user)
-
- {:ok, user} =
- User.upgrade_changeset(user, data)
- |> Repo.update()
-
- if !already_ap do
- # This could potentially take a long time, do it in the background
- if async do
- Task.start(fn ->
- user_upgrade_task(user)
- end)
- else
- user_upgrade_task(user)
- end
+ {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
+ already_ap <- User.ap_enabled?(user),
+ {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do
+ unless already_ap do
+ PleromaJobQueue.enqueue(:transmogrifier, __MODULE__, [:user_upgrade, user])
end
{:ok, user}
else
+ %User{} = user -> {:ok, user}
e -> e
end
end
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 32545937e..0b53f71c3 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -99,7 +99,10 @@ defmodule Pleroma.Web.ActivityPub.Utils do
%{
"@context" => [
"https://www.w3.org/ns/activitystreams",
- "#{Web.base_url()}/schemas/litepub-0.1.jsonld"
+ "#{Web.base_url()}/schemas/litepub-0.1.jsonld",
+ %{
+ "@language" => "und"
+ }
]
}
end
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index b3a09e49e..78bf31893 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -25,6 +25,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|> json(nickname)
end
+ def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
+ with %User{} = follower <- User.get_by_nickname(follower_nick),
+ %User{} = followed <- User.get_by_nickname(followed_nick) do
+ User.follow(follower, followed)
+ end
+
+ conn
+ |> json("ok")
+ end
+
+ def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do
+ with %User{} = follower <- User.get_by_nickname(follower_nick),
+ %User{} = followed <- User.get_by_nickname(followed_nick) do
+ User.unfollow(follower, followed)
+ end
+
+ conn
+ |> json("ok")
+ end
+
def user_create(
conn,
%{"nickname" => nickname, "email" => email, "password" => password}
diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex
index 82267c595..89d88af32 100644
--- a/lib/pleroma/web/auth/authenticator.ex
+++ b/lib/pleroma/web/auth/authenticator.ex
@@ -3,6 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.Authenticator do
+ alias Pleroma.Registration
alias Pleroma.User
def implementation do
@@ -12,14 +13,33 @@ defmodule Pleroma.Web.Auth.Authenticator do
)
end
- @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()}
- def get_user(plug), do: implementation().get_user(plug)
+ @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()}
+ def get_user(plug, params), do: implementation().get_user(plug, params)
+
+ @callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) ::
+ {:ok, User.t()} | {:error, any()}
+ def create_from_registration(plug, params, registration),
+ do: implementation().create_from_registration(plug, params, registration)
+
+ @callback get_registration(Plug.Conn.t(), Map.t()) ::
+ {:ok, Registration.t()} | {:error, any()}
+ def get_registration(plug, params),
+ do: implementation().get_registration(plug, params)
@callback handle_error(Plug.Conn.t(), any()) :: any()
def handle_error(plug, error), do: implementation().handle_error(plug, error)
@callback auth_template() :: String.t() | nil
def auth_template do
- implementation().auth_template() || Pleroma.Config.get(:auth_template, "show.html")
+ # Note: `config :pleroma, :auth_template, "..."` support is deprecated
+ implementation().auth_template() ||
+ Pleroma.Config.get([:auth, :auth_template], Pleroma.Config.get(:auth_template)) ||
+ "show.html"
+ end
+
+ @callback oauth_consumer_template() :: String.t() | nil
+ def oauth_consumer_template do
+ implementation().oauth_consumer_template() ||
+ Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
end
end
diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex
index 88217aab8..8b6d5a77f 100644
--- a/lib/pleroma/web/auth/ldap_authenticator.ex
+++ b/lib/pleroma/web/auth/ldap_authenticator.ex
@@ -8,14 +8,19 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
require Logger
@behaviour Pleroma.Web.Auth.Authenticator
+ @base Pleroma.Web.Auth.PleromaAuthenticator
@connection_timeout 10_000
@search_timeout 10_000
- def get_user(%Plug.Conn{} = conn) do
+ defdelegate get_registration(conn, params), to: @base
+
+ defdelegate create_from_registration(conn, params, registration), to: @base
+
+ def get_user(%Plug.Conn{} = conn, params) do
if Pleroma.Config.get([:ldap, :enabled]) do
{name, password} =
- case conn.params do
+ case params do
%{"authorization" => %{"name" => name, "password" => password}} ->
{name, password}
@@ -29,14 +34,14 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
{:error, {:ldap_connection_error, _}} ->
# When LDAP is unavailable, try default authenticator
- Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
+ @base.get_user(conn, params)
error ->
error
end
else
# Fall back to default authenticator
- Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn)
+ @base.get_user(conn, params)
end
end
@@ -46,6 +51,8 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
def auth_template, do: nil
+ def oauth_consumer_template, do: nil
+
defp ldap_user(name, password) do
ldap = Pleroma.Config.get(:ldap, [])
host = Keyword.get(ldap, :host, "localhost")
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
index 94a19ad49..c826adb4c 100644
--- a/lib/pleroma/web/auth/pleroma_authenticator.ex
+++ b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -4,13 +4,15 @@
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Comeonin.Pbkdf2
+ alias Pleroma.Registration
+ alias Pleroma.Repo
alias Pleroma.User
@behaviour Pleroma.Web.Auth.Authenticator
- def get_user(%Plug.Conn{} = conn) do
+ def get_user(%Plug.Conn{} = _conn, params) do
{name, password} =
- case conn.params do
+ case params do
%{"authorization" => %{"name" => name, "password" => password}} ->
{name, password}
@@ -27,9 +29,69 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
end
end
+ def get_registration(
+ %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}},
+ _params
+ ) do
+ registration = Registration.get_by_provider_uid(provider, uid)
+
+ if registration do
+ {:ok, registration}
+ else
+ info = auth.info
+
+ Registration.changeset(%Registration{}, %{
+ provider: to_string(provider),
+ uid: to_string(uid),
+ info: %{
+ "nickname" => info.nickname,
+ "email" => info.email,
+ "name" => info.name,
+ "description" => info.description
+ }
+ })
+ |> Repo.insert()
+ end
+ end
+
+ def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials}
+
+ def create_from_registration(_conn, params, registration) do
+ nickname = value([params["nickname"], Registration.nickname(registration)])
+ email = value([params["email"], Registration.email(registration)])
+ name = value([params["name"], Registration.name(registration)]) || nickname
+ bio = value([params["bio"], Registration.description(registration)])
+
+ random_password = :crypto.strong_rand_bytes(64) |> Base.encode64()
+
+ with {:ok, new_user} <-
+ User.register_changeset(
+ %User{},
+ %{
+ email: email,
+ nickname: nickname,
+ name: name,
+ bio: bio,
+ password: random_password,
+ password_confirmation: random_password
+ },
+ external: true,
+ confirmed: true
+ )
+ |> Repo.insert(),
+ {:ok, _} <-
+ Registration.changeset(registration, %{user_id: new_user.id}) |> Repo.update() do
+ {:ok, new_user}
+ end
+ end
+
+ defp value(list), do: Enum.find(list, &(to_string(&1) != ""))
+
def handle_error(%Plug.Conn{} = _conn, error) do
error
end
def auth_template, do: nil
+
+ def oauth_consumer_template, do: nil
end
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 745d1839b..74babdf14 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -167,7 +167,7 @@ defmodule Pleroma.Web.CommonAPI do
object,
"emoji",
(Formatter.get_emoji(status) ++ Formatter.get_emoji(data["spoiler_text"]))
- |> Enum.reduce(%{}, fn {name, file}, acc ->
+ |> Enum.reduce(%{}, fn {name, file, _}, acc ->
Map.put(acc, name, "#{Pleroma.Web.Endpoint.static_url()}#{file}")
end)
) do
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index a830ea5d2..7b9f0ea06 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -294,7 +294,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def emoji_from_profile(%{info: _info} = user) do
(Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
- |> Enum.map(fn {shortcode, url} ->
+ |> Enum.map(fn {shortcode, url, _} ->
%{
"type" => "Emoji",
"icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index fa2d1cbe7..1633477c3 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -51,11 +51,22 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.MethodOverride)
plug(Plug.Head)
+ secure_cookies = Pleroma.Config.get([__MODULE__, :secure_cookie_flag])
+
cookie_name =
- if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
+ if secure_cookies,
do: "__Host-pleroma_key",
else: "pleroma_key"
+ same_site =
+ if Pleroma.Config.oauth_consumer_enabled?() do
+ # Note: "SameSite=Strict" prevents sign in with external OAuth provider
+ # (there would be no cookies during callback request from OAuth provider)
+ "SameSite=Lax"
+ else
+ "SameSite=Strict"
+ end
+
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
@@ -65,11 +76,30 @@ defmodule Pleroma.Web.Endpoint do
key: cookie_name,
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
http_only: true,
- secure:
- Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
- extra: "SameSite=Strict"
+ secure: secure_cookies,
+ extra: same_site
)
+ # Note: the plug and its configuration is compile-time this can't be upstreamed yet
+ if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do
+ plug(RemoteIp, proxies: proxies)
+ end
+
+ defmodule Instrumenter do
+ use Prometheus.PhoenixInstrumenter
+ end
+
+ defmodule PipelineInstrumenter do
+ use Prometheus.PlugPipelineInstrumenter
+ end
+
+ defmodule MetricsExporter do
+ use Prometheus.PlugExporter
+ end
+
+ plug(PipelineInstrumenter)
+ plug(MetricsExporter)
+
plug(Pleroma.Web.Router)
@doc """
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index 08ea5f967..382f07e6b 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Pagination
+ alias Pleroma.ScheduledActivity
alias Pleroma.User
def get_followers(user, params \\ %{}) do
@@ -28,6 +29,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
|> Pagination.fetch_paginated(params)
end
+ def get_scheduled_activities(user, params \\ %{}) do
+ user
+ |> ScheduledActivity.for_user_query()
+ |> Pagination.fetch_paginated(params)
+ end
+
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string}
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index d9c0cd55e..ed082abdf 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -5,12 +5,14 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
+ alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Filter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
+ alias Pleroma.ScheduledActivity
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web
@@ -25,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web.MastodonAPI.MastodonView
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.ReportView
+ alias Pleroma.Web.MastodonAPI.ScheduledActivityView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.OAuth.App
@@ -178,14 +181,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
defp mastodonized_emoji do
Pleroma.Emoji.get_all()
- |> Enum.map(fn {shortcode, relative_url} ->
+ |> Enum.map(fn {shortcode, relative_url, tags} ->
url = to_string(URI.merge(Web.base_url(), relative_url))
%{
"shortcode" => shortcode,
"static_url" => url,
"visible_in_picker" => true,
- "url" => url
+ "url" => url,
+ "tags" => String.split(tags, ",")
}
end)
end
@@ -364,6 +368,55 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
+ def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
+ with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
+ conn
+ |> add_link_headers(:scheduled_statuses, scheduled_activities)
+ |> put_view(ScheduledActivityView)
+ |> render("index.json", %{scheduled_activities: scheduled_activities})
+ end
+ end
+
+ def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
+ with %ScheduledActivity{} = scheduled_activity <-
+ ScheduledActivity.get(user, scheduled_activity_id) do
+ conn
+ |> put_view(ScheduledActivityView)
+ |> render("show.json", %{scheduled_activity: scheduled_activity})
+ else
+ _ -> {:error, :not_found}
+ end
+ end
+
+ def update_scheduled_status(
+ %{assigns: %{user: user}} = conn,
+ %{"id" => scheduled_activity_id} = params
+ ) do
+ with %ScheduledActivity{} = scheduled_activity <-
+ ScheduledActivity.get(user, scheduled_activity_id),
+ {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
+ conn
+ |> put_view(ScheduledActivityView)
+ |> render("show.json", %{scheduled_activity: scheduled_activity})
+ else
+ nil -> {:error, :not_found}
+ error -> error
+ end
+ end
+
+ def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do
+ with %ScheduledActivity{} = scheduled_activity <-
+ ScheduledActivity.get(user, scheduled_activity_id),
+ {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
+ conn
+ |> put_view(ScheduledActivityView)
+ |> render("show.json", %{scheduled_activity: scheduled_activity})
+ else
+ nil -> {:error, :not_found}
+ error -> error
+ end
+ end
+
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
when length(media_ids) > 0 do
params =
@@ -384,12 +437,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
_ -> Ecto.UUID.generate()
end
- {:ok, activity} =
- Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ -> CommonAPI.post(user, params) end)
+ scheduled_at = params["scheduled_at"]
- conn
- |> put_view(StatusView)
- |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+ if scheduled_at && ScheduledActivity.far_enough?(scheduled_at) do
+ with {:ok, scheduled_activity} <-
+ ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
+ conn
+ |> put_view(ScheduledActivityView)
+ |> render("show.json", %{scheduled_activity: scheduled_activity})
+ end
+ else
+ params = Map.drop(params, ["scheduled_at"])
+
+ {:ok, activity} =
+ Cachex.fetch!(:idempotency_cache, idempotency_key, fn _ ->
+ CommonAPI.post(user, params)
+ end)
+
+ conn
+ |> put_view(StatusView)
+ |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+ end
end
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
@@ -1119,9 +1187,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def index(%{assigns: %{user: user}} = conn, _params) do
- token =
- conn
- |> get_session(:oauth_token)
+ token = get_session(conn, :oauth_token)
if user && token do
mastodon_emoji = mastodonized_emoji()
@@ -1222,6 +1288,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> render("index.html", %{initial_state: initial_state, flavour: flavour})
else
conn
+ |> put_session(:return_to, conn.request_path)
|> redirect(to: "/web/login")
end
end
@@ -1306,12 +1373,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
scope: Enum.join(app.scopes, " ")
)
- conn
- |> redirect(to: path)
+ redirect(conn, to: path)
end
end
- defp local_mastodon_root_path(conn), do: mastodon_api_path(conn, :index, ["getting-started"])
+ defp local_mastodon_root_path(conn) do
+ case get_session(conn, :return_to) do
+ nil ->
+ mastodon_api_path(conn, :index, ["getting-started"])
+
+ return_to ->
+ delete_session(conn, :return_to)
+ return_to
+ end
+ end
defp get_or_make_app do
find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."}
@@ -1427,6 +1502,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
# fallback action
#
+ def errors(conn, {:error, %Changeset{} = changeset}) do
+ error_message =
+ changeset
+ |> Changeset.traverse_errors(fn {message, _opt} -> message end)
+ |> Enum.map_join(", ", fn {_k, v} -> v end)
+
+ conn
+ |> put_status(422)
+ |> json(%{error: error_message})
+ end
+
+ def errors(conn, {:error, :not_found}) do
+ conn
+ |> put_status(404)
+ |> json(%{error: "Record not found"})
+ end
+
def errors(conn, _) do
conn
|> put_status(500)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 42595b0b5..af56c4149 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -118,13 +118,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
},
# Pleroma extension
- pleroma: %{
- confirmation_pending: user_info.confirmation_pending,
- tags: user.tags,
- is_moderator: user.info.is_moderator,
- is_admin: user.info.is_admin,
- relationship: relationship
- }
+ pleroma:
+ %{
+ confirmation_pending: user_info.confirmation_pending,
+ tags: user.tags,
+ is_moderator: user.info.is_moderator,
+ is_admin: user.info.is_admin,
+ relationship: relationship
+ }
+ |> with_notification_settings(user, opts[:for])
}
end
@@ -133,4 +135,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
defp username_from_nickname(_), do: nil
+
+ defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
+ Map.put(data, :notification_settings, user.info.notification_settings)
+ end
+
+ defp with_notification_settings(data, _, _), do: data
end
diff --git a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex
new file mode 100644
index 000000000..0aae15ab9
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ def render("index.json", %{scheduled_activities: scheduled_activities}) do
+ render_many(scheduled_activities, ScheduledActivityView, "show.json")
+ end
+
+ def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do
+ %{
+ id: to_string(scheduled_activity.id),
+ scheduled_at: CommonAPI.Utils.to_masto_date(scheduled_activity.scheduled_at),
+ params: status_params(scheduled_activity.params)
+ }
+ |> with_media_attachments(scheduled_activity)
+ end
+
+ defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do
+ try do
+ attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment)
+ Map.put(data, :media_attachments, attachments)
+ rescue
+ _ -> data
+ end
+ end
+
+ defp with_media_attachments(data, _), do: data
+
+ defp status_params(params) do
+ data = %{
+ text: params["status"],
+ sensitive: params["sensitive"],
+ spoiler_text: params["spoiler_text"],
+ visibility: params["visibility"],
+ scheduled_at: params["scheduled_at"],
+ poll: params["poll"],
+ in_reply_to_id: params["in_reply_to_id"]
+ }
+
+ data =
+ if media_ids = params["media_ids"] do
+ Map.put(data, :media_ids, media_ids)
+ else
+ data
+ end
+
+ data
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 200bb453d..d4a8e4fff 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -147,10 +147,37 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
content =
object
|> render_content()
- |> HTML.get_cached_scrubbed_html_for_object(
+
+ content_html =
+ content
+ |> HTML.get_cached_scrubbed_html_for_activity(
+ User.html_filter_policy(opts[:for]),
+ activity,
+ "mastoapi:content"
+ )
+
+ content_plaintext =
+ content
+ |> HTML.get_cached_stripped_html_for_activity(
+ activity,
+ "mastoapi:content"
+ )
+
+ summary = object["summary"] || ""
+
+ summary_html =
+ summary
+ |> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
- __MODULE__
+ "mastoapi:summary"
+ )
+
+ summary_plaintext =
+ summary
+ |> HTML.get_cached_stripped_html_for_activity(
+ activity,
+ "mastoapi:summary"
)
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
@@ -171,7 +198,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
card: card,
- content: content,
+ content: content_html,
created_at: created_at,
reblogs_count: announcement_count,
replies_count: object["repliesCount"] || 0,
@@ -182,7 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user),
sensitive: sensitive,
- spoiler_text: object["summary"] || "",
+ spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
mentions: mentions,
@@ -195,7 +222,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
emojis: build_emojis(activity.data["object"]["emoji"]),
pleroma: %{
local: activity.local,
- conversation_id: get_context_id(activity)
+ conversation_id: get_context_id(activity),
+ content: %{"text/plain" => content_plaintext},
+ spoiler_text: %{"text/plain" => summary_plaintext}
}
}
end
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index 23bbde1a6..58385a3d1 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.Metadata.Utils do
# html content comes from DB already encoded, decode first and scrub after
|> HtmlEntities.decode()
|> String.replace(~r/<br\s?\/?>/, " ")
- |> HTML.get_cached_stripped_html_for_object(object, __MODULE__)
+ |> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|> Formatter.demojify()
|> Formatter.truncate()
end
diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex
index f0fe3b578..afaa00242 100644
--- a/lib/pleroma/web/oauth/fallback_controller.ex
+++ b/lib/pleroma/web/oauth/fallback_controller.ex
@@ -6,8 +6,21 @@ defmodule Pleroma.Web.OAuth.FallbackController do
use Pleroma.Web, :controller
alias Pleroma.Web.OAuth.OAuthController
- # No user/password
- def call(conn, _) do
+ def call(conn, {:register, :generic_error}) do
+ conn
+ |> put_status(:internal_server_error)
+ |> put_flash(:error, "Unknown error, please check the details and try again.")
+ |> OAuthController.registration_details(conn.params)
+ end
+
+ def call(conn, {:register, _error}) do
+ conn
+ |> put_status(:unauthorized)
+ |> put_flash(:error, "Invalid Username/Password")
+ |> OAuthController.registration_details(conn.params)
+ end
+
+ def call(conn, _error) do
conn
|> put_status(:unauthorized)
|> put_flash(:error, "Invalid Username/Password")
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 26d53df1a..bee7084ad 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller
+ alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.Auth.Authenticator
@@ -15,6 +16,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
+ if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
+
plug(:fetch_session)
plug(:fetch_flash)
@@ -57,66 +60,65 @@ defmodule Pleroma.Web.OAuth.OAuthController do
})
end
- def create_authorization(conn, %{
- "authorization" =>
- %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri
- } = auth_params
- }) do
- with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
- %App{} = app <- Repo.get_by(App, client_id: client_id),
- true <- redirect_uri in String.split(app.redirect_uris),
- scopes <- oauth_scopes(auth_params, []),
- {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
- # Note: `scope` param is intentionally not optional in this context
- {:missing_scopes, false} <- {:missing_scopes, scopes == []},
- {:auth_active, true} <- {:auth_active, User.auth_active?(user)},
- {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
- redirect_uri = redirect_uri(conn, redirect_uri)
+ def create_authorization(
+ conn,
+ %{"authorization" => auth_params} = params,
+ opts \\ []
+ ) do
+ with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do
+ after_create_authorization(conn, auth, auth_params)
+ else
+ error ->
+ handle_create_authorization_error(conn, error, auth_params)
+ end
+ end
- cond do
- redirect_uri == "urn:ietf:wg:oauth:2.0:oob" ->
- render(conn, "results.html", %{
- auth: auth
- })
+ def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do
+ redirect_uri = redirect_uri(conn, redirect_uri)
- true ->
- connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
- url = "#{redirect_uri}#{connector}"
- url_params = %{:code => auth.token}
+ if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
+ render(conn, "results.html", %{
+ auth: auth
+ })
+ else
+ connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
+ url = "#{redirect_uri}#{connector}"
+ url_params = %{:code => auth.token}
- url_params =
- if auth_params["state"] do
- Map.put(url_params, :state, auth_params["state"])
- else
- url_params
- end
+ url_params =
+ if auth_params["state"] do
+ Map.put(url_params, :state, auth_params["state"])
+ else
+ url_params
+ end
- url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
+ url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
- redirect(conn, external: url)
- end
- else
- {scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] ->
- # Per https://github.com/tootsuite/mastodon/blob/
- # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
- conn
- |> put_flash(:error, "This action is outside the authorized scopes")
- |> put_status(:unauthorized)
- |> authorize(auth_params)
+ redirect(conn, external: url)
+ end
+ end
- {:auth_active, false} ->
- # Per https://github.com/tootsuite/mastodon/blob/
- # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
- conn
- |> put_flash(:error, "Your login is missing a confirmed e-mail address")
- |> put_status(:forbidden)
- |> authorize(auth_params)
+ defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params)
+ when scopes_issue in [:unsupported_scopes, :missing_scopes] do
+ # Per https://github.com/tootsuite/mastodon/blob/
+ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
+ conn
+ |> put_flash(:error, "This action is outside the authorized scopes")
+ |> put_status(:unauthorized)
+ |> authorize(auth_params)
+ end
- error ->
- Authenticator.handle_error(conn, error)
- end
+ defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do
+ # Per https://github.com/tootsuite/mastodon/blob/
+ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
+ conn
+ |> put_flash(:error, "Your login is missing a confirmed e-mail address")
+ |> put_status(:forbidden)
+ |> authorize(auth_params)
+ end
+
+ defp handle_create_authorization_error(conn, error, _auth_params) do
+ Authenticator.handle_error(conn, error)
end
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
@@ -149,9 +151,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
conn,
%{"grant_type" => "password"} = params
) do
- with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},
+ with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)},
%App{} = app <- get_app_from_request(conn, params),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
+ {:user_active, true} <- {:user_active, !user.info.deactivated},
scopes <- oauth_scopes(params, app.scopes),
[] <- scopes -- app.scopes,
true <- Enum.any?(scopes),
@@ -175,6 +178,11 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> put_status(:forbidden)
|> json(%{error: "Your login is missing a confirmed e-mail address"})
+ {:user_active, false} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(%{error: "Your account is currently disabled"})
+
_error ->
put_status(conn, 400)
|> json(%{error: "Invalid credentials"})
@@ -205,6 +213,184 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
+ @doc "Prepares OAuth request to provider for Ueberauth"
+ def prepare_request(conn, %{"provider" => provider} = params) do
+ scope =
+ oauth_scopes(params, [])
+ |> Enum.join(" ")
+
+ state =
+ params
+ |> Map.delete("scopes")
+ |> Map.put("scope", scope)
+ |> Poison.encode!()
+
+ params =
+ params
+ |> Map.drop(~w(scope scopes client_id redirect_uri))
+ |> Map.put("state", state)
+
+ # Handing the request to Ueberauth
+ redirect(conn, to: o_auth_path(conn, :request, provider, params))
+ end
+
+ def request(conn, params) do
+ message =
+ if params["provider"] do
+ "Unsupported OAuth provider: #{params["provider"]}."
+ else
+ "Bad OAuth request."
+ end
+
+ conn
+ |> put_flash(:error, message)
+ |> redirect(to: "/")
+ end
+
+ def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
+ params = callback_params(params)
+ messages = for e <- Map.get(failure, :errors, []), do: e.message
+ message = Enum.join(messages, "; ")
+
+ conn
+ |> put_flash(:error, "Failed to authenticate: #{message}.")
+ |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
+ end
+
+ def callback(conn, params) do
+ params = callback_params(params)
+
+ with {:ok, registration} <- Authenticator.get_registration(conn, params) do
+ user = Repo.preload(registration, :user).user
+ auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state))
+
+ if user do
+ create_authorization(
+ conn,
+ %{"authorization" => auth_params},
+ user: user
+ )
+ else
+ registration_params =
+ Map.merge(auth_params, %{
+ "nickname" => Registration.nickname(registration),
+ "email" => Registration.email(registration)
+ })
+
+ conn
+ |> put_session(:registration_id, registration.id)
+ |> registration_details(registration_params)
+ end
+ else
+ _ ->
+ conn
+ |> put_flash(:error, "Failed to set up user account.")
+ |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
+ end
+ end
+
+ defp callback_params(%{"state" => state} = params) do
+ Map.merge(params, Poison.decode!(state))
+ end
+
+ def registration_details(conn, params) do
+ render(conn, "register.html", %{
+ client_id: params["client_id"],
+ redirect_uri: params["redirect_uri"],
+ state: params["state"],
+ scopes: oauth_scopes(params, []),
+ nickname: params["nickname"],
+ email: params["email"]
+ })
+ end
+
+ def register(conn, %{"op" => "connect"} = params) do
+ authorization_params = Map.put(params, "name", params["auth_name"])
+ create_authorization_params = %{"authorization" => authorization_params}
+
+ with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
+ %Registration{} = registration <- Repo.get(Registration, registration_id),
+ {_, {:ok, auth}} <-
+ {:create_authorization, do_create_authorization(conn, create_authorization_params)},
+ %User{} = user <- Repo.preload(auth, :user).user,
+ {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
+ conn
+ |> put_session_registration_id(nil)
+ |> after_create_authorization(auth, authorization_params)
+ else
+ {:create_authorization, error} ->
+ {:register, handle_create_authorization_error(conn, error, create_authorization_params)}
+
+ _ ->
+ {:register, :generic_error}
+ end
+ end
+
+ def register(conn, %{"op" => "register"} = params) do
+ with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
+ %Registration{} = registration <- Repo.get(Registration, registration_id),
+ {:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do
+ conn
+ |> put_session_registration_id(nil)
+ |> create_authorization(
+ %{
+ "authorization" => %{
+ "client_id" => params["client_id"],
+ "redirect_uri" => params["redirect_uri"],
+ "scopes" => oauth_scopes(params, nil)
+ }
+ },
+ user: user
+ )
+ else
+ {:error, changeset} ->
+ message =
+ Enum.map(changeset.errors, fn {field, {error, _}} ->
+ "#{field} #{error}"
+ end)
+ |> Enum.join("; ")
+
+ message =
+ String.replace(
+ message,
+ "ap_id has already been taken",
+ "nickname has already been taken"
+ )
+
+ conn
+ |> put_status(:forbidden)
+ |> put_flash(:error, "Error: #{message}.")
+ |> registration_details(params)
+
+ _ ->
+ {:register, :generic_error}
+ end
+ end
+
+ defp do_create_authorization(
+ conn,
+ %{
+ "authorization" =>
+ %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri
+ } = auth_params
+ } = params,
+ user \\ nil
+ ) do
+ with {_, {:ok, %User{} = user}} <-
+ {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)},
+ %App{} = app <- Repo.get_by(App, client_id: client_id),
+ true <- redirect_uri in String.split(app.redirect_uris),
+ scopes <- oauth_scopes(auth_params, []),
+ {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},
+ # Note: `scope` param is intentionally not optional in this context
+ {:missing_scopes, false} <- {:missing_scopes, scopes == []},
+ {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do
+ Authorization.create_authorization(app, user, scopes)
+ end
+ end
+
# XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
# decoding it. Investigate sometime.
defp fix_padding(token) do
@@ -242,4 +428,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
defp redirect_uri(_conn, redirect_uri), do: redirect_uri
+
+ defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
+
+ defp put_session_registration_id(conn, registration_id),
+ do: put_session(conn, :registration_id, registration_id)
end
diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex
index 863573185..2233480c5 100644
--- a/lib/pleroma/web/push/impl.ex
+++ b/lib/pleroma/web/push/impl.ex
@@ -19,8 +19,8 @@ defmodule Pleroma.Web.Push.Impl do
@types ["Create", "Follow", "Announce", "Like"]
@doc "Performs sending notifications for user subscriptions"
- @spec perform_send(Notification.t()) :: list(any)
- def perform_send(
+ @spec perform(Notification.t()) :: list(any) | :error
+ def perform(
%{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} =
notif
)
@@ -50,7 +50,7 @@ defmodule Pleroma.Web.Push.Impl do
end
end
- def perform_send(_) do
+ def perform(_) do
Logger.warn("Unknown notification type")
:error
end
diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex
index 5259e8e33..729dad02a 100644
--- a/lib/pleroma/web/push/push.ex
+++ b/lib/pleroma/web/push/push.ex
@@ -3,18 +3,20 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Push do
- use GenServer
-
alias Pleroma.Web.Push.Impl
require Logger
- ##############
- # Client API #
- ##############
+ def init do
+ unless enabled() do
+ Logger.warn("""
+ VAPID key pair is not found. If you wish to enabled web push, please run
+
+ mix web_push.gen.keypair
- def start_link do
- GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ and add the resulting output to your configuration file.
+ """)
+ end
end
def vapid_config do
@@ -30,35 +32,5 @@ defmodule Pleroma.Web.Push do
end
def send(notification),
- do: GenServer.cast(__MODULE__, {:send, notification})
-
- ####################
- # Server Callbacks #
- ####################
-
- @impl true
- def init(:ok) do
- if enabled() do
- {:ok, nil}
- else
- Logger.warn("""
- VAPID key pair is not found. If you wish to enabled web push, please run
-
- mix web_push.gen.keypair
-
- and add the resulting output to your configuration file.
- """)
-
- :ignore
- end
- end
-
- @impl true
- def handle_cast({:send, notification}, state) do
- if enabled() do
- Impl.perform_send(notification)
- end
-
- {:noreply, state}
- end
+ do: PleromaJobQueue.enqueue(:web_push, Impl, [notification])
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index da988e5f2..06c8d7a03 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -5,6 +5,11 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
+ pipeline :browser do
+ plug(:accepts, ["html"])
+ plug(:fetch_session)
+ end
+
pipeline :oauth do
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
@@ -140,8 +145,12 @@ 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)
+
delete("/user", AdminAPIController, :user_delete)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
post("/user", AdminAPIController, :user_create)
@@ -184,6 +193,7 @@ defmodule Pleroma.Web.Router do
post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account)
+ put("/notification_settings", UtilController, :update_notificaton_settings)
end
scope [] do
@@ -209,6 +219,16 @@ defmodule Pleroma.Web.Router do
post("/authorize", OAuthController, :create_authorization)
post("/token", OAuthController, :token_exchange)
post("/revoke", OAuthController, :token_revoke)
+ get("/registration_details", OAuthController, :registration_details)
+
+ scope [] do
+ pipe_through(:browser)
+
+ get("/prepare_request", OAuthController, :prepare_request)
+ get("/:provider", OAuthController, :request)
+ get("/:provider/callback", OAuthController, :callback)
+ post("/register", OAuthController, :register)
+ end
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@@ -240,6 +260,9 @@ defmodule Pleroma.Web.Router do
get("/notifications", MastodonAPIController, :notifications)
get("/notifications/:id", MastodonAPIController, :get_notification)
+ get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
+ get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
+
get("/lists", MastodonAPIController, :get_lists)
get("/lists/:id", MastodonAPIController, :get_list)
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
@@ -274,6 +297,9 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
+ put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
+ delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
+
post("/media", MastodonAPIController, :upload)
put("/media/:id", MastodonAPIController, :update_media)
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
new file mode 100644
index 000000000..4b8fb5dae
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex
@@ -0,0 +1,13 @@
+<div class="scopes-input">
+ <%= label @form, :scope, "Permissions" %>
+
+ <div class="scopes">
+ <%= for scope <- @available_scopes do %>
+ <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
+ <div class="scope">
+ <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: assigns[:scope_param] || "scope[]" %>
+ <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>
+ </div>
+ <% end %>
+ </div>
+</div>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
new file mode 100644
index 000000000..85f62ca64
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex
@@ -0,0 +1,13 @@
+<h2>Sign in with external provider</h2>
+
+<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %>
+ <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>
+
+ <%= hidden_input f, :client_id, value: @client_id %>
+ <%= hidden_input f, :redirect_uri, value: @redirect_uri %>
+ <%= hidden_input f, :state, value: @state %>
+
+ <%= for strategy <- Pleroma.Config.oauth_consumer_strategies() do %>
+ <%= submit "Sign in with #{String.capitalize(strategy)}", name: "provider", value: strategy %>
+ <% end %>
+<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
new file mode 100644
index 000000000..126390391
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex
@@ -0,0 +1,43 @@
+<%= if get_flash(@conn, :info) do %>
+ <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
+<% end %>
+<%= if get_flash(@conn, :error) do %>
+ <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
+<% end %>
+
+<h2>Registration Details</h2>
+
+<p>If you'd like to register a new account, please provide the details below.</p>
+
+<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %>
+
+<div class="input">
+ <%= label f, :nickname, "Nickname" %>
+ <%= text_input f, :nickname, value: @nickname %>
+</div>
+<div class="input">
+ <%= label f, :email, "Email" %>
+ <%= text_input f, :email, value: @email %>
+</div>
+
+<%= submit "Proceed as new user", name: "op", value: "register" %>
+
+<p>Alternatively, sign in to connect to existing account.</p>
+
+<div class="input">
+ <%= label f, :auth_name, "Name or email" %>
+ <%= text_input f, :auth_name %>
+</div>
+<div class="input">
+ <%= label f, :password, "Password" %>
+ <%= password_input f, :password %>
+</div>
+
+<%= submit "Proceed as existing user", name: "op", value: "connect" %>
+
+<%= hidden_input f, :client_id, value: @client_id %>
+<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
+<%= hidden_input f, :scope, value: Enum.join(@scopes, " ") %>
+<%= hidden_input f, :state, value: @state %>
+
+<% end %>
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
index 161333847..87278e636 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
@@ -4,7 +4,9 @@
<%= if get_flash(@conn, :error) do %>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<% end %>
+
<h2>OAuth Authorization</h2>
+
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
<div class="input">
<%= label f, :name, "Name or email" %>
@@ -14,22 +16,16 @@
<%= label f, :password, "Password" %>
<%= password_input f, :password %>
</div>
-<div class="scopes-input">
-<%= label f, :scope, "Permissions" %>
- <div class="scopes">
- <%= for scope <- @available_scopes do %>
- <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>
- <div class="scope">
- <%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>
- <%= label f, :"scope_#{scope}", String.capitalize(scope) %>
- </div>
- <% end %>
- </div>
-</div>
+
+<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f, scope_param: "authorization[scope][]"}) %>
<%= hidden_input f, :client_id, value: @client_id %>
<%= hidden_input f, :response_type, value: @response_type %>
<%= hidden_input f, :redirect_uri, value: @redirect_uri %>
-<%= hidden_input f, :state, value: @state%>
+<%= hidden_input f, :state, value: @state %>
<%= submit "Authorize" %>
<% end %>
+
+<%= if Pleroma.Config.oauth_consumer_enabled?() do %>
+ <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %>
+<% end %>
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 3cdd7a2f2..d066d35f5 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -283,7 +283,20 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def emoji(conn, _params) do
- json(conn, Enum.into(Emoji.get_all(), %{}))
+ emoji =
+ Emoji.get_all()
+ |> Enum.map(fn {short_code, path, tags} ->
+ {short_code, %{image_url: path, tags: String.split(tags, ",")}}
+ end)
+ |> Enum.into(%{})
+
+ json(conn, emoji)
+ end
+
+ def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, _} <- User.update_notification_settings(user, params) do
+ json(conn, %{status: "success"})
+ end
end
def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index aa1d41fa2..433322eb8 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -254,10 +254,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
html =
content
- |> HTML.get_cached_scrubbed_html_for_object(
+ |> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
- __MODULE__
+ "twitterapi:content"
)
|> Formatter.emojify(object["emoji"])
@@ -265,7 +265,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
if content do
content
|> String.replace(~r/<br\s?\/?>/, "\n")
- |> HTML.get_cached_stripped_html_for_object(activity, __MODULE__)
+ |> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
else
""
end