X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Fmastodon_api%2Fcontrollers%2Faccount_controller.ex;h=73fad519ecbe1259f3ab0782beaddbf818943bcc;hb=791bcfd90f41da9d77ab5a5ad6eec22ae8050b8a;hp=844de2e799612401d8315bbf72252914f0b96982;hpb=e7aef27c0011d3fd0b569ebdb9196a1e011eae5d;p=akkoma diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 844de2e79..73fad519e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -5,26 +5,236 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, truthy_param?: 1] + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] + alias Pleroma.Emoji + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Plugs.RateLimiter alias Pleroma.User - alias Pleroma.Web.CommonAPI alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.MastodonAPI.StatusView - alias Pleroma.Web.MastodonAPI.MastodonAPI + alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.ListView - alias Pleroma.Plugs.RateLimiter + alias Pleroma.Web.MastodonAPI.MastodonAPI + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.TwitterAPI.TwitterAPI + + plug( + OAuthScopesPlug, + %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} + when action == :show + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:accounts"]} + when action in [:endorsements, :verify_credentials, :followers, :following] + ) - require Pleroma.Constants + plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) - @relations ~w(follow unfollow)a + plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists) + + plug( + OAuthScopesPlug, + %{scopes: ["follow", "read:blocks"]} when action == :blocks + ) + + plug( + OAuthScopesPlug, + %{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock] + ) + + plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships) + + # Note: :follows (POST /api/v1/follows) is the same as :follow, consider removing :follows + plug( + OAuthScopesPlug, + %{scopes: ["follow", "write:follows"]} when action in [:follows, :follow, :unfollow] + ) + + plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes) + + plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) + + plug( + Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug + when action != :create + ) + + @relations [:follow, :unfollow] + @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a plug(RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @relations) plug(RateLimiter, :relations_actions when action in @relations) - plug(:assign_account when action not in [:show, :statuses, :follows]) + plug(RateLimiter, :app_account_creation when action == :create) + plug(:assign_account_by_id when action in @needs_account) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + @doc "POST /api/v1/accounts" + def create( + %{assigns: %{app: app}} = conn, + %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params + ) do + params = + params + |> Map.take([ + "email", + "captcha_solution", + "captcha_token", + "captcha_answer_data", + "token", + "password" + ]) + |> Map.put("nickname", nickname) + |> Map.put("fullname", params["fullname"] || nickname) + |> Map.put("bio", params["bio"] || "") + |> Map.put("confirm", params["password"]) + + with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), + {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do + json(conn, %{ + token_type: "Bearer", + access_token: token.token, + scope: app.scopes, + created_at: Token.Utils.format_created_at(token) + }) + else + {:error, errors} -> json_response(conn, :bad_request, errors) + end + end + + def create(%{assigns: %{app: _app}} = conn, _) do + render_error(conn, :bad_request, "Missing parameters") + end + + def create(conn, _) do + render_error(conn, :forbidden, "Invalid credentials") + end + + @doc "GET /api/v1/accounts/verify_credentials" + def verify_credentials(%{assigns: %{user: user}} = conn, _) do + chat_token = Phoenix.Token.sign(conn, "user socket", user.id) + + render(conn, "show.json", + user: user, + for: user, + with_pleroma_settings: true, + with_chat_token: chat_token + ) + end + + @doc "PATCH /api/v1/accounts/update_credentials" + def update_credentials(%{assigns: %{user: original_user}} = conn, params) do + user = original_user + + params = + if Map.has_key?(params, "fields_attributes") do + Map.update!(params, "fields_attributes", fn fields -> + fields + |> normalize_fields_attributes() + |> Enum.filter(fn %{"name" => n} -> n != "" end) + end) + else + params + end + + user_params = + [ + :no_rich_text, + :locked, + :hide_followers_count, + :hide_follows_count, + :hide_followers, + :hide_follows, + :hide_favorites, + :show_role, + :skip_thread_containment, + :discoverable + ] + |> Enum.reduce(%{}, fn key, acc -> + add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)}) + end) + |> add_if_present(params, "display_name", :name) + |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end) + |> add_if_present(params, "avatar", :avatar, fn value -> + with %Plug.Upload{} <- value, + {:ok, object} <- ActivityPub.upload(value, type: :avatar) do + {:ok, object.data} + end + end) + |> add_if_present(params, "header", :banner, fn value -> + with %Plug.Upload{} <- value, + {:ok, object} <- ActivityPub.upload(value, type: :banner) do + {:ok, object.data} + end + end) + |> add_if_present(params, "pleroma_background_image", :background, fn value -> + with %Plug.Upload{} <- value, + {:ok, object} <- ActivityPub.upload(value, type: :background) do + {:ok, object.data} + end + end) + |> add_if_present(params, "fields_attributes", :fields, fn fields -> + fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) + + {:ok, fields} + end) + |> add_if_present(params, "fields_attributes", :raw_fields) + |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value -> + {:ok, Map.merge(user.pleroma_settings_store, value)} + end) + |> add_if_present(params, "default_scope", :default_scope) + + emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") + + user_emojis = + user + |> Map.get(:emoji, []) + |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text)) + |> Enum.dedup() + + user_params = Map.put(user_params, :emoji, user_emojis) + changeset = User.update_changeset(user, user_params) + + with {:ok, user} <- User.update_and_set_cache(changeset) do + if original_user != user, do: CommonAPI.update(user) + + render(conn, "show.json", user: user, for: user, with_pleroma_settings: true) + else + _e -> render_error(conn, :forbidden, "Invalid request") + end + end + + defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do + with true <- Map.has_key?(params, params_field), + {:ok, new_value} <- value_function.(params[params_field]) do + Map.put(map, map_field, new_value) + else + _ -> map + end + end + + defp normalize_fields_attributes(fields) do + if Enum.all?(fields, &is_tuple/1) do + Enum.map(fields, fn {_, v} -> v end) + else + fields + end + end + + @doc "GET /api/v1/accounts/relationships" + def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do + targets = User.get_all_by_ids(List.wrap(id)) + + render(conn, "relationships.json", user: user, targets: targets) + end + + # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array. + def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) + @doc "GET /api/v1/accounts/:id" def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), @@ -53,7 +263,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do followers = cond do for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params) - user.info.hide_followers -> [] + user.hide_followers -> [] true -> MastodonAPI.get_followers(user, params) end @@ -67,7 +277,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do followers = cond do for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params) - user.info.hide_follows -> [] + user.hide_follows -> [] true -> MastodonAPI.get_friends(user, params) end @@ -85,60 +295,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do |> render("index.json", lists: lists) end - @doc "GET /api/v1/pleroma/accounts/:id/favourites" - def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do - render_error(conn, :forbidden, "Can't get favorites") - end - - def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do - params = - params - |> Map.put("type", "Create") - |> Map.put("favorited_by", user.ap_id) - |> Map.put("blocking_user", for_user) - - recipients = - if for_user do - [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following] - else - [Pleroma.Constants.as_public()] - end - - activities = - recipients - |> ActivityPub.fetch_activities(params) - |> Enum.reverse() - - conn - |> add_link_headers(activities) - |> put_view(StatusView) - |> render("index.json", activities: activities, for: for_user, as: :activity) - end - - @doc "POST /api/v1/pleroma/accounts/:id/subscribe" - def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do - render(conn, "relationship.json", user: user, target: subscription_target) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - - @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" - def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do - with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do - render(conn, "relationship.json", user: user, target: subscription_target) - else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) - end - end - @doc "POST /api/v1/accounts/:id/follow" def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do {:error, :not_found} @@ -148,14 +304,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do render(conn, "relationship.json", user: follower, target: followed) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end - @doc "POST /api/v1/pleroma/:id/unfollow" + @doc "POST /api/v1/accounts/:id/unfollow" def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do {:error, :not_found} end @@ -173,10 +326,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do with {:ok, muter} <- User.mute(muter, muted, notifications?) do render(conn, "relationship.json", user: muter, target: muted) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end @@ -185,10 +335,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do with {:ok, muter} <- User.unmute(muter, muted) do render(conn, "relationship.json", user: muter, target: muted) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end @@ -198,10 +345,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:ok, _activity} <- ActivityPub.block(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end @@ -211,17 +355,33 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do render(conn, "relationship.json", user: blocker, target: blocked) else - {:error, message} -> - conn - |> put_status(:forbidden) - |> json(%{error: message}) + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end - defp assign_account(%{params: %{"id" => id}} = conn, _) do - case User.get_cached_by_id(id) do - %User{} = account -> assign(conn, :account, account) - nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() + @doc "POST /api/v1/follows" + def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do + with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, + {_, true} <- {:followed, follower.id != followed.id}, + {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do + render(conn, "show.json", user: followed, for: follower) + else + {:followed, _} -> {:error, :not_found} + {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end + + @doc "GET /api/v1/mutes" + def mutes(%{assigns: %{user: user}} = conn, _) do + render(conn, "index.json", users: User.muted_users(user), for: user, as: :user) + end + + @doc "GET /api/v1/blocks" + def blocks(%{assigns: %{user: user}} = conn, _) do + render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user) + end + + @doc "GET /api/v1/endorsements" + def endorsements(conn, params), + do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params) end