aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/pleroma/conversation/participation.ex10
-rw-r--r--lib/pleroma/object.ex88
-rw-r--r--lib/pleroma/plugs/oauth_scopes_plug.ex9
-rw-r--r--lib/pleroma/uploaders/local.ex13
-rw-r--r--lib/pleroma/uploaders/mdii.ex37
-rw-r--r--lib/pleroma/uploaders/s3.ex14
-rw-r--r--lib/pleroma/uploaders/uploader.ex3
-rw-r--r--lib/pleroma/user.ex59
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex4
-rw-r--r--lib/pleroma/web/activity_pub/relay.ex4
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex25
-rw-r--r--lib/pleroma/web/metadata/utils.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex2
-rw-r--r--lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex9
-rw-r--r--lib/pleroma/web/router.ex4
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex11
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex14
-rw-r--r--lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex (renamed from lib/pleroma/web/templates/twitter_api/util/followed.html.eex)0
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/follow.html.eex11
-rw-r--r--lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex14
-rw-r--r--lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex112
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex104
-rw-r--r--lib/pleroma/web/twitter_api/views/remote_follow_view.ex10
23 files changed, 361 insertions, 198 deletions
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index aafe57280..e5d28ebff 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -64,11 +64,13 @@ defmodule Pleroma.Conversation.Participation do
end
def mark_as_read(participation) do
- participation
- |> read_cng(%{read: true})
- |> Repo.update()
+ __MODULE__
+ |> where(id: ^participation.id)
+ |> update(set: [read: true])
+ |> select([p], p)
+ |> Repo.update_all([])
|> case do
- {:ok, participation} ->
+ {1, [participation]} ->
participation = Repo.preload(participation, :user)
User.set_unread_conversation_count(participation.user)
{:ok, participation}
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index eb37b95a6..2452a7389 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -17,6 +17,8 @@ defmodule Pleroma.Object do
require Logger
+ @type t() :: %__MODULE__{}
+
schema "objects" do
field(:data, :map)
@@ -79,6 +81,20 @@ defmodule Pleroma.Object do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end
+ @doc """
+ Get a single attachment by it's name and href
+ """
+ @spec get_attachment_by_name_and_href(String.t(), String.t()) :: Object.t() | nil
+ def get_attachment_by_name_and_href(name, href) do
+ query =
+ from(o in Object,
+ where: fragment("(?)->>'name' = ?", o.data, ^name),
+ where: fragment("(?)->>'href' = ?", o.data, ^href)
+ )
+
+ Repo.one(query)
+ end
+
defp warn_on_no_object_preloaded(ap_id) do
"Object.normalize() called without preloaded object (#{inspect(ap_id)}). Consider preloading the object"
|> Logger.debug()
@@ -164,6 +180,7 @@ defmodule Pleroma.Object do
def delete(%Object{data: %{"id" => id}} = object) do
with {:ok, _obj} = swap_object_with_tombstone(object),
+ :ok <- delete_attachments(object),
deleted_activity = Activity.delete_all_by_object_ap_id(id),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
{:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do
@@ -171,6 +188,77 @@ defmodule Pleroma.Object do
end
end
+ defp delete_attachments(%{data: %{"attachment" => [_ | _] = attachments, "actor" => actor}}) do
+ hrefs =
+ Enum.flat_map(attachments, fn attachment ->
+ Enum.map(attachment["url"], & &1["href"])
+ end)
+
+ names = Enum.map(attachments, & &1["name"])
+
+ uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
+
+ # find all objects for copies of the attachments, name and actor doesn't matter here
+ delete_ids =
+ from(o in Object,
+ where:
+ fragment(
+ "to_jsonb(array(select jsonb_array_elements((?)#>'{url}') ->> 'href'))::jsonb \\?| (?)",
+ o.data,
+ ^hrefs
+ )
+ )
+ |> Repo.all()
+ # we should delete 1 object for any given attachment, but don't delete files if
+ # there are more than 1 object for it
+ |> Enum.reduce(%{}, fn %{
+ id: id,
+ data: %{
+ "url" => [%{"href" => href}],
+ "actor" => obj_actor,
+ "name" => name
+ }
+ },
+ acc ->
+ Map.update(acc, href, %{id: id, count: 1}, fn val ->
+ case obj_actor == actor and name in names do
+ true ->
+ # set id of the actor's object that will be deleted
+ %{val | id: id, count: val.count + 1}
+
+ false ->
+ # another actor's object, just increase count to not delete file
+ %{val | count: val.count + 1}
+ end
+ end)
+ end)
+ |> Enum.map(fn {href, %{id: id, count: count}} ->
+ # only delete files that have single instance
+ with 1 <- count do
+ prefix =
+ case Pleroma.Config.get([Pleroma.Upload, :base_url]) do
+ nil -> "media"
+ _ -> ""
+ end
+
+ base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url())
+
+ file_path = String.trim_leading(href, "#{base_url}/#{prefix}")
+
+ uploader.delete_file(file_path)
+ end
+
+ id
+ end)
+
+ from(o in Object, where: o.id in ^delete_ids)
+ |> Repo.delete_all()
+
+ :ok
+ end
+
+ defp delete_attachments(%{data: _data}), do: :ok
+
def prune(%Object{data: %{"id" => id}} = object) do
with {:ok, object} <- Repo.delete(object),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}"),
diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex
index 174a8389c..07c0f7fdb 100644
--- a/lib/pleroma/plugs/oauth_scopes_plug.ex
+++ b/lib/pleroma/plugs/oauth_scopes_plug.ex
@@ -18,16 +18,13 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
token = assigns[:token]
scopes = transform_scopes(scopes, options)
- matched_scopes = token && filter_descendants(scopes, token.scopes)
+ matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
cond do
- is_nil(token) ->
- maybe_perform_instance_privacy_check(conn, options)
-
- op == :| && Enum.any?(matched_scopes) ->
+ token && op == :| && Enum.any?(matched_scopes) ->
conn
- op == :& && matched_scopes == scopes ->
+ token && op == :& && matched_scopes == scopes ->
conn
options[:fallback] == :proceed_unauthenticated ->
diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex
index 36b3c35ec..2e6fe3292 100644
--- a/lib/pleroma/uploaders/local.ex
+++ b/lib/pleroma/uploaders/local.ex
@@ -5,10 +5,12 @@
defmodule Pleroma.Uploaders.Local do
@behaviour Pleroma.Uploaders.Uploader
+ @impl true
def get_file(_) do
{:ok, {:static_dir, upload_path()}}
end
+ @impl true
def put_file(upload) do
{local_path, file} =
case Enum.reverse(Path.split(upload.path)) do
@@ -33,4 +35,15 @@ defmodule Pleroma.Uploaders.Local do
def upload_path do
Pleroma.Config.get!([__MODULE__, :uploads])
end
+
+ @impl true
+ def delete_file(path) do
+ upload_path()
+ |> Path.join(path)
+ |> File.rm()
+ |> case do
+ :ok -> :ok
+ {:error, posix_error} -> {:error, to_string(posix_error)}
+ end
+ end
end
diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex
deleted file mode 100644
index c36f3d61d..000000000
--- a/lib/pleroma/uploaders/mdii.ex
+++ /dev/null
@@ -1,37 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Uploaders.MDII do
- @moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure"
-
- alias Pleroma.Config
- alias Pleroma.HTTP
-
- @behaviour Pleroma.Uploaders.Uploader
-
- # MDII-hosted images are never passed through the MediaPlug; only local media.
- # Delegate to Pleroma.Uploaders.Local
- def get_file(file) do
- Pleroma.Uploaders.Local.get_file(file)
- end
-
- def put_file(upload) do
- cgi = Config.get([Pleroma.Uploaders.MDII, :cgi])
- files = Config.get([Pleroma.Uploaders.MDII, :files])
-
- {:ok, file_data} = File.read(upload.tempfile)
-
- extension = String.split(upload.name, ".") |> List.last()
- query = "#{cgi}?#{extension}"
-
- with {:ok, %{status: 200, body: body}} <-
- HTTP.post(query, file_data, [], adapter: [pool: :default]) do
- remote_file_name = String.split(body) |> List.first()
- public_url = "#{files}/#{remote_file_name}.#{extension}"
- {:ok, {:url, public_url}}
- else
- _ -> Pleroma.Uploaders.Local.put_file(upload)
- end
- end
-end
diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex
index 9876b6398..feb89cea6 100644
--- a/lib/pleroma/uploaders/s3.ex
+++ b/lib/pleroma/uploaders/s3.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Uploaders.S3 do
# The file name is re-encoded with S3's constraints here to comply with previous
# links with less strict filenames
+ @impl true
def get_file(file) do
config = Config.get([__MODULE__])
bucket = Keyword.fetch!(config, :bucket)
@@ -35,6 +36,7 @@ defmodule Pleroma.Uploaders.S3 do
])}}
end
+ @impl true
def put_file(%Pleroma.Upload{} = upload) do
config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket)
@@ -69,6 +71,18 @@ defmodule Pleroma.Uploaders.S3 do
end
end
+ @impl true
+ def delete_file(file) do
+ [__MODULE__, :bucket]
+ |> Config.get()
+ |> ExAws.S3.delete_object(file)
+ |> ExAws.request()
+ |> case do
+ {:ok, %{status_code: 204}} -> :ok
+ error -> {:error, inspect(error)}
+ end
+ end
+
@regex Regex.compile!("[^0-9a-zA-Z!.*/'()_-]")
def strict_encode(name) do
String.replace(name, @regex, "-")
diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex
index c0b22c28a..d71e213d2 100644
--- a/lib/pleroma/uploaders/uploader.ex
+++ b/lib/pleroma/uploaders/uploader.ex
@@ -36,6 +36,8 @@ defmodule Pleroma.Uploaders.Uploader do
@callback put_file(Pleroma.Upload.t()) ::
:ok | {:ok, file_spec()} | {:error, String.t()} | :wait_callback
+ @callback delete_file(file :: String.t()) :: :ok | {:error, String.t()}
+
@callback http_callback(Plug.Conn.t(), Map.t()) ::
{:ok, Plug.Conn.t()}
| {:ok, Plug.Conn.t(), file_spec()}
@@ -43,7 +45,6 @@ defmodule Pleroma.Uploaders.Uploader do
@optional_callbacks http_callback: 2
@spec put_file(module(), Pleroma.Upload.t()) :: {:ok, file_spec()} | {:error, String.t()}
-
def put_file(uploader, upload) do
case uploader.put_file(upload) do
:ok -> {:ok, {:file, upload.path}}
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 706aee2ff..2e225415c 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1430,20 +1430,47 @@ defmodule Pleroma.User do
Creates an internal service actor by URI if missing.
Optionally takes nickname for addressing.
"""
- def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
- with user when is_nil(user) <- get_cached_by_ap_id(uri) do
- {:ok, user} =
- %User{
- invisible: true,
- local: true,
- ap_id: uri,
- nickname: nickname,
- follower_address: uri <> "/followers"
- }
- |> Repo.insert()
+ @spec get_or_create_service_actor_by_ap_id(String.t(), String.t()) :: User.t() | nil
+ def get_or_create_service_actor_by_ap_id(uri, nickname) do
+ {_, user} =
+ case get_cached_by_ap_id(uri) do
+ nil ->
+ with {:error, %{errors: errors}} <- create_service_actor(uri, nickname) do
+ Logger.error("Cannot create service actor: #{uri}/.\n#{inspect(errors)}")
+ {:error, nil}
+ end
- user
- end
+ %User{invisible: false} = user ->
+ set_invisible(user)
+
+ user ->
+ {:ok, user}
+ end
+
+ user
+ end
+
+ @spec set_invisible(User.t()) :: {:ok, User.t()}
+ defp set_invisible(user) do
+ user
+ |> change(%{invisible: true})
+ |> update_and_set_cache()
+ end
+
+ @spec create_service_actor(String.t(), String.t()) ::
+ {:ok, User.t()} | {:error, Ecto.Changeset.t()}
+ defp create_service_actor(uri, nickname) do
+ %User{
+ invisible: true,
+ local: true,
+ ap_id: uri,
+ nickname: nickname,
+ follower_address: uri <> "/followers"
+ }
+ |> change
+ |> unique_constraint(:nickname)
+ |> Repo.insert()
+ |> set_cache()
end
# AP style
@@ -1855,9 +1882,9 @@ defmodule Pleroma.User do
])
with {:ok, updated_user} <- update_and_set_cache(changeset) do
- if user.is_admin && !updated_user.is_admin do
- # Tokens & authorizations containing any admin scopes must be revoked (revoking all).
- # This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins).
+ if user.is_admin != updated_user.is_admin do
+ # Admin status change results in change of accessible OAuth scopes, and instead of changing
+ # already issued tokens we revoke them, requiring user to sign in again
global_sign_out(user)
end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index db072bad2..e4e3ab44a 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -264,6 +264,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
"rel" => "self",
"type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"href" => user.ap_id
+ },
+ %{
+ "rel" => "http://ostatus.org/schema/1.0/subscribe",
+ "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}"
}
]
end
diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex
index 99a804568..48a1b71e0 100644
--- a/lib/pleroma/web/activity_pub/relay.ex
+++ b/lib/pleroma/web/activity_pub/relay.ex
@@ -9,10 +9,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
alias Pleroma.Web.ActivityPub.ActivityPub
require Logger
+ @relay_nickname "relay"
+
def get_actor do
actor =
relay_ap_id()
- |> User.get_or_create_service_actor_by_ap_id()
+ |> User.get_or_create_service_actor_by_ap_id(@relay_nickname)
actor
end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
index ca261ad6e..9f7e4943c 100644
--- a/lib/pleroma/web/masto_fe_controller.ex
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -20,18 +20,21 @@ defmodule Pleroma.Web.MastoFEController do
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
@doc "GET /web/*path"
- def index(%{assigns: %{user: user}} = conn, _params) do
- token = get_session(conn, :oauth_token)
+ def index(%{assigns: %{user: user, token: token}} = conn, _params)
+ when not is_nil(user) and not is_nil(token) do
+ conn
+ |> put_layout(false)
+ |> render("index.html",
+ token: token.token,
+ user: user,
+ custom_emojis: Pleroma.Emoji.get_all()
+ )
+ end
- if user && token do
- conn
- |> put_layout(false)
- |> render("index.html", token: token, user: user, custom_emojis: Pleroma.Emoji.get_all())
- else
- conn
- |> put_session(:return_to, conn.request_path)
- |> redirect(to: "/web/login")
- end
+ def index(conn, _params) do
+ conn
+ |> put_session(:return_to, conn.request_path)
+ |> redirect(to: "/web/login")
end
@doc "GET /web/manifest.json"
diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex
index 382ecf426..589d11901 100644
--- a/lib/pleroma/web/metadata/utils.ex
+++ b/lib/pleroma/web/metadata/utils.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.Metadata.Utils do
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.get_cached_stripped_html_for_activity(object, "metadata")
|> Emoji.Formatter.demojify()
+ |> HtmlEntities.decode()
|> Formatter.truncate()
end
@@ -25,6 +26,7 @@ defmodule Pleroma.Web.Metadata.Utils do
|> String.replace(~r/<br\s?\/?>/, " ")
|> HTML.strip_tags()
|> Emoji.Formatter.demojify()
+ |> HtmlEntities.decode()
|> Formatter.truncate(max_length)
end
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
index 69dfa92e3..0bbf84fd3 100644
--- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex
@@ -52,7 +52,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
@doc """
Lists the packs available on the instance as JSON.
- The information is public and does not require authentification. The format is
+ The information is public and does not require authentication. The format is
a map of "pack directory name" to pack.json contents.
"""
def list_packs(conn, _params) do
diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
index 8fed3f5bb..772c535a4 100644
--- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
+++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex
@@ -22,7 +22,14 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
plug(
OAuthScopesPlug,
- %{scopes: ["read:statuses"]} when action in [:conversation, :conversation_statuses]
+ %{scopes: ["read:statuses"]}
+ when action in [:conversation, :conversation_statuses, :emoji_reactions_by]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:statuses"]}
+ when action in [:react_with_emoji, :unreact_with_emoji]
)
plug(
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index f6c128283..9654ab8a3 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -229,9 +229,9 @@ defmodule Pleroma.Web.Router do
pipe_through(:pleroma_html)
post("/main/ostatus", UtilController, :remote_subscribe)
- get("/ostatus_subscribe", UtilController, :remote_follow)
+ get("/ostatus_subscribe", RemoteFollowController, :follow)
- post("/ostatus_subscribe", UtilController, :do_remote_follow)
+ post("/ostatus_subscribe", RemoteFollowController, :do_follow)
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
new file mode 100644
index 000000000..5ba192cd7
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex
@@ -0,0 +1,11 @@
+<%= if @error == :error do %>
+ <h2>Error fetching user</h2>
+<% else %>
+ <h2>Remote follow</h2>
+ <img height="128" width="128" src="<%= avatar_url(@followee) %>">
+ <p><%= @followee.nickname %></p>
+ <%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %>
+ <%= hidden_input f, :id, value: @followee.id %>
+ <%= submit "Authorize" %>
+ <% end %>
+<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
new file mode 100644
index 000000000..df44988ee
--- /dev/null
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex
@@ -0,0 +1,14 @@
+<%= if @error do %>
+<h2><%= @error %></h2>
+<% end %>
+<h2>Log in to follow</h2>
+<p><%= @followee.nickname %></p>
+<img height="128" width="128" src="<%= avatar_url(@followee) %>">
+<%= form_for @conn, remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %>
+<%= text_input f, :name, placeholder: "Username", required: true %>
+<br>
+<%= password_input f, :password, placeholder: "Password", required: true %>
+<br>
+<%= hidden_input f, :id, value: @followee.id %>
+<%= submit "Authorize" %>
+<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/followed.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
index da473d502..da473d502 100644
--- a/lib/pleroma/web/templates/twitter_api/util/followed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex
diff --git a/lib/pleroma/web/templates/twitter_api/util/follow.html.eex b/lib/pleroma/web/templates/twitter_api/util/follow.html.eex
deleted file mode 100644
index 06359fa6c..000000000
--- a/lib/pleroma/web/templates/twitter_api/util/follow.html.eex
+++ /dev/null
@@ -1,11 +0,0 @@
-<%= if @error == :error do %>
- <h2>Error fetching user</h2>
-<% else %>
- <h2>Remote follow</h2>
- <img width="128" height="128" src="<%= @avatar %>">
- <p><%= @name %></p>
- <%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "user"], fn f -> %>
- <%= hidden_input f, :id, value: @id %>
- <%= submit "Authorize" %>
- <% end %>
-<% end %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex
deleted file mode 100644
index 4e3a2be67..000000000
--- a/lib/pleroma/web/templates/twitter_api/util/follow_login.html.eex
+++ /dev/null
@@ -1,14 +0,0 @@
-<%= if @error do %>
- <h2><%= @error %></h2>
-<% end %>
-<h2>Log in to follow</h2>
-<p><%= @name %></p>
-<img height="128" width="128" src="<%= @avatar %>">
-<%= form_for @conn, util_path(@conn, :do_remote_follow), [as: "authorization"], fn f -> %>
-<%= text_input f, :name, placeholder: "Username" %>
-<br>
-<%= password_input f, :password, placeholder: "Password" %>
-<br>
-<%= hidden_input f, :id, value: @id %>
-<%= submit "Authorize" %>
-<% end %>
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
new file mode 100644
index 000000000..e0d4d5632
--- /dev/null
+++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -0,0 +1,112 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
+ use Pleroma.Web, :controller
+
+ require Logger
+
+ alias Pleroma.Activity
+ alias Pleroma.Object.Fetcher
+ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
+ alias Pleroma.Web.Auth.Authenticator
+ alias Pleroma.Web.CommonAPI
+
+ @status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
+
+ # Note: follower can submit the form (with password auth) not being signed in (having no token)
+ plug(
+ OAuthScopesPlug,
+ %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
+ when action in [:do_follow]
+ )
+
+ # GET /ostatus_subscribe
+ #
+ def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
+ case is_status?(acct) do
+ true -> follow_status(conn, user, acct)
+ _ -> follow_account(conn, user, acct)
+ end
+ end
+
+ defp follow_status(conn, _user, acct) do
+ with {:ok, object} <- Fetcher.fetch_object_from_id(acct),
+ %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do
+ redirect(conn, to: o_status_path(conn, :notice, activity_id))
+ else
+ error ->
+ handle_follow_error(conn, error)
+ end
+ end
+
+ defp follow_account(conn, user, acct) do
+ with {:ok, followee} <- User.get_or_fetch(acct) do
+ render(conn, follow_template(user), %{error: false, followee: followee, acct: acct})
+ else
+ {:error, _reason} ->
+ render(conn, follow_template(user), %{error: :error})
+ end
+ end
+
+ defp follow_template(%User{} = _user), do: "follow.html"
+ defp follow_template(_), do: "follow_login.html"
+
+ defp is_status?(acct) do
+ case Fetcher.fetch_and_contain_remote_object_from_id(acct) do
+ {:ok, %{"type" => type}} when type in @status_types ->
+ true
+
+ _ ->
+ false
+ end
+ end
+
+ # POST /ostatus_subscribe
+ #
+ def do_follow(%{assigns: %{user: %User{} = user}} = conn, %{"user" => %{"id" => id}}) do
+ with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
+ {:ok, _, _, _} <- CommonAPI.follow(user, followee) do
+ render(conn, "followed.html", %{error: false})
+ else
+ error ->
+ handle_follow_error(conn, error)
+ end
+ end
+
+ def do_follow(conn, %{"authorization" => %{"name" => _, "password" => _, "id" => id}}) do
+ with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
+ {_, {:ok, user}, _} <- {:auth, Authenticator.get_user(conn), followee},
+ {:ok, _, _, _} <- CommonAPI.follow(user, followee) do
+ render(conn, "followed.html", %{error: false})
+ else
+ error ->
+ handle_follow_error(conn, error)
+ end
+ end
+
+ def do_follow(%{assigns: %{user: nil}} = conn, _) do
+ Logger.debug("Insufficient permissions: follow | write:follows.")
+ render(conn, "followed.html", %{error: "Insufficient permissions: follow | write:follows."})
+ end
+
+ defp handle_follow_error(conn, {:auth, _, followee} = _) do
+ render(conn, "follow_login.html", %{error: "Wrong username or password", followee: followee})
+ end
+
+ defp handle_follow_error(conn, {:fetch_user, error} = _) do
+ Logger.debug("Remote follow failed with error #{inspect(error)}")
+ render(conn, "followed.html", %{error: "Could not find user"})
+ end
+
+ defp handle_follow_error(conn, {:error, "Could not follow user:" <> _} = _) do
+ render(conn, "followed.html", %{error: "Error following account"})
+ end
+
+ defp handle_follow_error(conn, error) do
+ Logger.debug("Remote follow failed with error #{inspect(error)}")
+ render(conn, "followed.html", %{error: "Something went wrong."})
+ end
+end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 799dd17ae..f08b9d28c 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -7,12 +7,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
require Logger
- alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Emoji
alias Pleroma.Healthcheck
alias Pleroma.Notification
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web
@@ -22,7 +20,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]}
- when action in [:do_remote_follow, :follow_import]
+ when action == :follow_import
+ )
+
+ # Note: follower can submit the form (with password auth) not being signed in (having no token)
+ plug(
+ OAuthScopesPlug,
+ %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
+ when action == :do_remote_follow
)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
@@ -77,95 +82,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
- def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do
- if is_status?(acct) do
- {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)
- %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])
- redirect(conn, to: "/notice/#{activity_id}")
- else
- with {:ok, followee} <- User.get_or_fetch(acct) do
- conn
- |> render(follow_template(user), %{
- error: false,
- acct: acct,
- avatar: User.avatar_url(followee),
- name: followee.nickname,
- id: followee.id
- })
- else
- {:error, _reason} ->
- render(conn, follow_template(user), %{error: :error})
- end
- end
- end
-
- defp follow_template(%User{} = _user), do: "follow.html"
- defp follow_template(_), do: "follow_login.html"
-
- defp is_status?(acct) do
- case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do
- {:ok, %{"type" => type}}
- when type in ["Article", "Event", "Note", "Video", "Page", "Question"] ->
- true
-
- _ ->
- false
- end
- end
-
- def do_remote_follow(conn, %{
- "authorization" => %{"name" => username, "password" => password, "id" => id}
- }) do
- with %User{} = followee <- User.get_cached_by_id(id),
- {_, %User{} = user, _} <- {:auth, User.get_cached_by_nickname(username), followee},
- {_, true, _} <- {
- :auth,
- AuthenticationPlug.checkpw(password, user.password_hash),
- followee
- },
- {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
- conn
- |> render("followed.html", %{error: false})
- else
- # Was already following user
- {:error, "Could not follow user:" <> _rest} ->
- render(conn, "followed.html", %{error: "Error following account"})
-
- {:auth, _, followee} ->
- conn
- |> render("follow_login.html", %{
- error: "Wrong username or password",
- id: id,
- name: followee.nickname,
- avatar: User.avatar_url(followee)
- })
-
- e ->
- Logger.debug("Remote follow failed with error #{inspect(e)}")
- render(conn, "followed.html", %{error: "Something went wrong."})
- end
- end
-
- def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do
- with {:fetch_user, %User{} = followee} <- {:fetch_user, User.get_cached_by_id(id)},
- {:ok, _follower, _followee, _activity} <- CommonAPI.follow(user, followee) do
- conn
- |> render("followed.html", %{error: false})
- else
- # Was already following user
- {:error, "Could not follow user:" <> _rest} ->
- render(conn, "followed.html", %{error: "Error following account"})
-
- {:fetch_user, error} ->
- Logger.debug("Remote follow failed with error #{inspect(error)}")
- render(conn, "followed.html", %{error: "Could not find user"})
-
- e ->
- Logger.debug("Remote follow failed with error #{inspect(e)}")
- render(conn, "followed.html", %{error: "Something went wrong."})
- end
- end
-
def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, _} <- Notification.read_one(user, notification_id) do
json(conn, %{status: "success"})
@@ -346,7 +262,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
def delete_account(%{assigns: %{user: user}} = conn, params) do
- case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
+ password = params["password"] || ""
+
+ case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} ->
User.delete(user)
json(conn, %{status: "success"})
diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
new file mode 100644
index 000000000..d469c4726
--- /dev/null
+++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML.Form
+
+ defdelegate avatar_url(user), to: Pleroma.User
+end