From d0eb43b58b0a191b727360cf4523329d2dc60adc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 16 Jul 2020 22:19:17 -0500 Subject: [PATCH 01/14] Add account aliases --- docs/API/pleroma_api.md | 20 ++++++++ lib/pleroma/user.ex | 24 ++++++++++ .../operations/pleroma_account_operation.ex | 46 +++++++++++++++++++ lib/pleroma/web/api_spec/schemas/account.ex | 2 + .../web/mastodon_api/views/account_view.ex | 1 + .../controllers/account_controller.ex | 25 ++++++++++ lib/pleroma/web/router.ex | 3 ++ lib/pleroma/web/web_finger/web_finger.ex | 9 +++- .../20200717025041_add_aliases_to_users.exs | 9 ++++ test/user_test.exs | 37 +++++++++++++++ .../mastodon_api/views/account_view_test.exs | 5 +- .../controllers/account_controller_test.exs | 29 ++++++++++++ .../web_finger/web_finger_controller_test.exs | 14 +++++- 13 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 priv/repo/migrations/20200717025041_add_aliases_to_users.exs diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 5bd38ad36..8a937fdfd 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -570,3 +570,23 @@ Emoji reactions work a lot like favourites do. They make it possible to react to {"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]} ] ``` + +# Account aliases + +Set and delete ActivityPub aliases for follower move. + +## `POST /api/v1/pleroma/accounts/ap_aliases` +### Add account aliases +* Method: `POST` +* Authentication: required +* Params: + * `aliases`: array of ActivityPub IDs to add +* Response: JSON, the user's account + +## `DELETE /api/v1/pleroma/accounts/ap_aliases` +### Delete account aliases +* Method: `DELETE` +* Authentication: required +* Params: + * `aliases`: array of ActivityPub IDs to delete +* Response: JSON, the user's account diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9240e912d..9b756c9a0 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -89,6 +89,7 @@ defmodule Pleroma.User do field(:keys, :string) field(:public_key, :string) field(:ap_id, :string) + field(:ap_aliases, {:array, :string}, default: []) field(:avatar, :map, default: %{}) field(:local, :boolean, default: true) field(:follower_address, :string) @@ -2268,4 +2269,27 @@ def sanitize_html(%User{} = user, filter) do |> Map.put(:bio, HTML.filter_tags(user.bio, filter)) |> Map.put(:fields, fields) end + + def add_aliases(%User{} = user, aliases) when is_list(aliases) do + alias_set = + (user.ap_aliases ++ aliases) + |> MapSet.new() + |> MapSet.to_list() + + user + |> change(%{ap_aliases: alias_set}) + |> Repo.update() + end + + def delete_aliases(%User{} = user, aliases) when is_list(aliases) do + alias_set = + user.ap_aliases + |> MapSet.new() + |> MapSet.difference(MapSet.new(aliases)) + |> MapSet.to_list() + + user + |> change(%{ap_aliases: alias_set}) + |> Repo.update() + end end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex index 97836b2eb..1040f6e20 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex @@ -4,6 +4,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.FlakeID @@ -87,10 +89,54 @@ def unsubscribe_operation do } end + def add_aliases_operation do + %Operation{ + tags: ["Accounts"], + summary: "Add ActivityPub aliases", + operationId: "PleromaAPI.AccountController.add_aliases", + requestBody: request_body("Parameters", alias_request(), required: true), + security: [%{"oAuth" => ["write:accounts"]}], + responses: %{ + 200 => Operation.response("Account", "application/json", Account), + 403 => Operation.response("Forbidden", "application/json", ApiError) + } + } + end + + def delete_aliases_operation do + %Operation{ + tags: ["Accounts"], + summary: "Delete ActivityPub aliases", + operationId: "PleromaAPI.AccountController.delete_aliases", + requestBody: request_body("Parameters", alias_request(), required: true), + security: [%{"oAuth" => ["write:accounts"]}], + responses: %{ + 200 => Operation.response("Account", "application/json", Account) + } + } + end + defp id_param do Operation.parameter(:id, :path, FlakeID, "Account ID", example: "9umDrYheeY451cQnEe", required: true ) end + + defp alias_request do + %Schema{ + title: "AccountAliasRequest", + description: "POST body for adding/deleting AP aliases", + type: :object, + properties: %{ + aliases: %Schema{ + type: :array, + items: %Schema{type: :string} + } + }, + example: %{ + "aliases" => ["https://beepboop.social/users/beep", "https://mushroom.kingdom/users/toad"] + } + } + end end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index ca79f0747..4fd27edf5 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -40,6 +40,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do pleroma: %Schema{ type: :object, properties: %{ + ap_id: %Schema{type: :string}, + ap_aliases: %Schema{type: :array, items: %Schema{type: :string}}, allow_following_move: %Schema{ type: :boolean, description: "whether the user allows automatically follow moved following accounts" diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index bc9745044..e2912031a 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -248,6 +248,7 @@ defp do_render("show.json", %{user: user} = opts) do # Pleroma extension pleroma: %{ ap_id: user.ap_id, + ap_aliases: user.ap_aliases, confirmation_pending: user.confirmation_pending, tags: user.tags, hide_followers_count: user.hide_followers_count, diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 563edded7..03e5781a3 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -39,6 +39,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites ) + plug( + OAuthScopesPlug, + %{scopes: ["write:accounts"]} when action in [:add_aliases, :delete_aliases] + ) + plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend) plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) @@ -107,4 +112,24 @@ def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end + + @doc "POST /api/v1/pleroma/accounts/ap_aliases" + def add_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params) + when is_list(aliases) do + with {:ok, user} <- User.add_aliases(user, aliases) do + render(conn, "show.json", user: user) + else + {:error, message} -> json_response(conn, :forbidden, %{error: message}) + end + end + + @doc "DELETE /api/v1/pleroma/accounts/ap_aliases" + def delete_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params) + when is_list(aliases) do + with {:ok, user} <- User.delete_aliases(user, aliases) do + render(conn, "show.json", user: user) + else + {:error, message} -> json_response(conn, :forbidden, %{error: message}) + end + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 386308362..dea95cd77 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -344,6 +344,9 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/subscribe", AccountController, :subscribe) post("/accounts/:id/unsubscribe", AccountController, :unsubscribe) + + post("/accounts/ap_aliases", AccountController, :add_aliases) + delete("/accounts/ap_aliases", AccountController, :delete_aliases) end post("/accounts/confirmation_resend", AccountController, :confirmation_resend) diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 71ccf251a..fb142ce8d 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -58,12 +58,19 @@ defp gather_links(%User{} = user) do ] ++ Publisher.gather_webfinger_links(user) end + defp gather_aliases(%User{} = user) do + user.ap_aliases + |> MapSet.new() + |> MapSet.put(user.ap_id) + |> MapSet.to_list() + end + def represent_user(user, "JSON") do {:ok, user} = User.ensure_keys_present(user) %{ "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}", - "aliases" => [user.ap_id], + "aliases" => gather_aliases(user), "links" => gather_links(user) } end diff --git a/priv/repo/migrations/20200717025041_add_aliases_to_users.exs b/priv/repo/migrations/20200717025041_add_aliases_to_users.exs new file mode 100644 index 000000000..a6ace6e0f --- /dev/null +++ b/priv/repo/migrations/20200717025041_add_aliases_to_users.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddAliasesToUsers do + use Ecto.Migration + + def change do + alter table(:users) do + add(:ap_aliases, {:array, :string}, default: []) + end + end +end diff --git a/test/user_test.exs b/test/user_test.exs index 9788e09d9..db6e4872e 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1858,4 +1858,41 @@ test "avatar fallback" do assert User.avatar_url(user, no_default: true) == nil end + + test "add_aliases/2" do + user = insert(:user) + + aliases = [ + "https://gleasonator.com/users/alex", + "https://gleasonator.com/users/alex", + "https://animalliberation.social/users/alex" + ] + + {:ok, user} = User.add_aliases(user, aliases) + + assert user.ap_aliases == [ + "https://animalliberation.social/users/alex", + "https://gleasonator.com/users/alex" + ] + end + + test "delete_aliases/2" do + user = + insert(:user, + ap_aliases: [ + "https://animalliberation.social/users/alex", + "https://benis.social/users/benis", + "https://gleasonator.com/users/alex" + ] + ) + + aliases = ["https://benis.social/users/benis"] + + {:ok, user} = User.delete_aliases(user, aliases) + + assert user.ap_aliases == [ + "https://animalliberation.social/users/alex", + "https://gleasonator.com/users/alex" + ] + end end diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index a83bf90a3..4a0512e68 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -37,7 +37,8 @@ test "Represent a user account" do "valid html. a
b
c
d
f '&<>\"", inserted_at: ~N[2017-08-15 15:47:06.597036], emoji: %{"karjalanpiirakka" => "/file.png"}, - raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"" + raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"", + ap_aliases: ["https://shitposter.zone/users/shp"] }) expected = %{ @@ -77,6 +78,7 @@ test "Represent a user account" do }, pleroma: %{ ap_id: user.ap_id, + ap_aliases: ["https://shitposter.zone/users/shp"], background_image: "https://example.com/images/asuka_hospital.png", favicon: "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png", @@ -171,6 +173,7 @@ test "Represent a Service(bot) account" do }, pleroma: %{ ap_id: user.ap_id, + ap_aliases: [], background_image: nil, favicon: "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png", diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs index 07909d48b..da01a8218 100644 --- a/test/web/pleroma_api/controllers/account_controller_test.exs +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -281,4 +281,33 @@ test "returns 404 when subscription_target not found" do assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404) end end + + describe "aliases controllers" do + setup do: oauth_access(["write:accounts"]) + + test "adds aliases", %{conn: conn} do + aliases = ["https://gleasonator.com/users/alex"] + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases}) + + assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200) + assert Enum.count(res) == 1 + end + + test "deletes aliases", %{conn: conn, user: user} do + aliases = ["https://gleasonator.com/users/alex"] + User.add_aliases(user, aliases) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> delete("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases}) + + assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200) + assert Enum.count(res) == 0 + end + end end diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs index 0023f1e81..50b6c4b3e 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -30,14 +30,24 @@ test "GET host-meta" do end test "Webfinger JRD" do - user = insert(:user) + user = + insert(:user, + ap_id: "https://hyrule.world/users/zelda", + ap_aliases: ["https://mushroom.kingdom/users/toad"] + ) response = build_conn() |> put_req_header("accept", "application/jrd+json") |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") + |> json_response(200) - assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost" + assert response["subject"] == "acct:#{user.nickname}@localhost" + + assert response["aliases"] == [ + "https://hyrule.world/users/zelda", + "https://mushroom.kingdom/users/toad" + ] end test "it returns 404 when user isn't found (JSON)" do From bd1e2e3a58ebd702306e7a6e2df985ac07e5f7d8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 17 Jul 2020 19:11:28 -0500 Subject: [PATCH 02/14] Validate alias IDs --- CHANGELOG.md | 1 + lib/pleroma/user.ex | 13 +++++++++++++ test/user_test.exs | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02f28241..ef3235804 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Support pagination in emoji packs API (for packs and for files in pack) - Support for viewing instances favicons next to posts and accounts - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. +- Ability to set ActivityPub aliases for follower migration.
API Changes diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 9b756c9a0..66664235b 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -47,6 +47,8 @@ defmodule Pleroma.User do # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ + # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength + @url_regex ~r/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/ @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ @@ -2278,6 +2280,7 @@ def add_aliases(%User{} = user, aliases) when is_list(aliases) do user |> change(%{ap_aliases: alias_set}) + |> validate_ap_aliases() |> Repo.update() end @@ -2290,6 +2293,16 @@ def delete_aliases(%User{} = user, aliases) when is_list(aliases) do user |> change(%{ap_aliases: alias_set}) + |> validate_ap_aliases() |> Repo.update() end + + defp validate_ap_aliases(changeset) do + validate_change(changeset, :ap_aliases, fn :ap_aliases, ap_aliases -> + case Enum.all?(ap_aliases, fn a -> Regex.match?(@url_regex, a) end) do + true -> [] + false -> [ap_aliases: "Invalid ap_id format. Must be a URL."] + end + end) + end end diff --git a/test/user_test.exs b/test/user_test.exs index db6e4872e..29855b9cd 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1876,6 +1876,13 @@ test "add_aliases/2" do ] end + test "add_aliases/2 with invalid alias" do + user = insert(:user) + {:error, _} = User.add_aliases(user, ["invalid_alias"]) + {:error, _} = User.add_aliases(user, ["http://still_invalid"]) + {:error, _} = User.add_aliases(user, ["http://validalias.com/users/dude", "invalid_alias"]) + end + test "delete_aliases/2" do user = insert(:user, From 4af1b803811cbb59d41f0149706d6dda340b4755 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 7 Aug 2020 16:48:03 -0500 Subject: [PATCH 03/14] Clean up account aliases --- docs/API/differences_in_mastoapi_responses.md | 1 + docs/API/pleroma_api.md | 20 -------- lib/pleroma/user.ex | 37 +++------------ .../api_spec/operations/account_operation.ex | 7 +++ .../operations/pleroma_account_operation.ex | 46 ------------------- lib/pleroma/web/api_spec/schemas/account.ex | 2 +- .../controllers/account_controller.ex | 2 + .../web/mastodon_api/views/account_view.ex | 2 +- .../controllers/account_controller.ex | 25 ---------- lib/pleroma/web/router.ex | 3 -- lib/pleroma/web/web_finger/web_finger.ex | 14 +++--- .../20200717025041_add_aliases_to_users.exs | 9 ---- test/user_test.exs | 44 ------------------ .../update_credentials_test.exs | 10 ++++ .../mastodon_api/views/account_view_test.exs | 6 +-- .../controllers/account_controller_test.exs | 29 ------------ .../web_finger/web_finger_controller_test.exs | 12 +++-- 17 files changed, 47 insertions(+), 222 deletions(-) delete mode 100644 priv/repo/migrations/20200717025041_add_aliases_to_users.exs diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 38865dc68..3cb2183bd 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -184,6 +184,7 @@ Additional parameters can be added to the JSON body/Form data: - `pleroma_settings_store` - Opaque user settings to be saved on the backend. - `skip_thread_containment` - if true, skip filtering out broken threads - `allow_following_move` - if true, allows automatically follow moved following accounts +- `also_known_as` - array of ActivityPub IDs, needed for following move - `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset. - `discoverable` - if true, discovery of this account in search results and other services is allowed. - `actor_type` - the type of this account. diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index c1aa4d204..4e97d26c0 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -570,23 +570,3 @@ Emoji reactions work a lot like favourites do. They make it possible to react to {"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]} ] ``` - -# Account aliases - -Set and delete ActivityPub aliases for follower move. - -## `POST /api/v1/pleroma/accounts/ap_aliases` -### Add account aliases -* Method: `POST` -* Authentication: required -* Params: - * `aliases`: array of ActivityPub IDs to add -* Response: JSON, the user's account - -## `DELETE /api/v1/pleroma/accounts/ap_aliases` -### Delete account aliases -* Method: `DELETE` -* Authentication: required -* Params: - * `aliases`: array of ActivityPub IDs to delete -* Response: JSON, the user's account diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ad7a04f62..57e06bd5a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -96,7 +96,6 @@ defmodule Pleroma.User do field(:keys, :string) field(:public_key, :string) field(:ap_id, :string) - field(:ap_aliases, {:array, :string}, default: []) field(:avatar, :map, default: %{}) field(:local, :boolean, default: true) field(:follower_address, :string) @@ -486,6 +485,7 @@ def update_changeset(struct, params \\ %{}) do :hide_follows_count, :hide_favorites, :allow_following_move, + :also_known_as, :background, :show_role, :skip_thread_containment, @@ -494,12 +494,12 @@ def update_changeset(struct, params \\ %{}) do :pleroma_settings_store, :discoverable, :actor_type, - :also_known_as, :accepts_chat_messages ] ) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) + |> validate_also_known_as() |> validate_length(:bio, max: bio_limit) |> validate_length(:name, min: 1, max: name_limit) |> validate_inclusion(:actor_type, ["Person", "Service"]) @@ -2387,36 +2387,11 @@ def sanitize_html(%User{} = user, filter) do |> Map.put(:fields, fields) end - def add_aliases(%User{} = user, aliases) when is_list(aliases) do - alias_set = - (user.ap_aliases ++ aliases) - |> MapSet.new() - |> MapSet.to_list() - - user - |> change(%{ap_aliases: alias_set}) - |> validate_ap_aliases() - |> Repo.update() - end - - def delete_aliases(%User{} = user, aliases) when is_list(aliases) do - alias_set = - user.ap_aliases - |> MapSet.new() - |> MapSet.difference(MapSet.new(aliases)) - |> MapSet.to_list() - - user - |> change(%{ap_aliases: alias_set}) - |> validate_ap_aliases() - |> Repo.update() - end - - defp validate_ap_aliases(changeset) do - validate_change(changeset, :ap_aliases, fn :ap_aliases, ap_aliases -> - case Enum.all?(ap_aliases, fn a -> Regex.match?(@url_regex, a) end) do + defp validate_also_known_as(changeset) do + validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as -> + case Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do true -> [] - false -> [ap_aliases: "Invalid ap_id format. Must be a URL."] + false -> [also_known_as: "Invalid ap_id format. Must be a URL."] end end) end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index aaebc9b5c..91b4d0c80 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -597,6 +597,12 @@ defp update_credentials_request do nullable: true, description: "Allows automatically follow moved following accounts" }, + also_known_as: %Schema{ + type: :array, + items: %Schema{type: :string}, + nullable: true, + description: "List of alternate ActivityPub IDs" + }, pleroma_background_image: %Schema{ type: :string, nullable: true, @@ -627,6 +633,7 @@ defp update_credentials_request do pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}}, skip_thread_containment: false, allow_following_move: false, + also_known_as: ["https://foo.bar/users/foo"], discoverable: false, actor_type: "Person" } diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex index 1040f6e20..97836b2eb 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex @@ -4,8 +4,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do alias OpenApiSpex.Operation - alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.FlakeID @@ -89,54 +87,10 @@ def unsubscribe_operation do } end - def add_aliases_operation do - %Operation{ - tags: ["Accounts"], - summary: "Add ActivityPub aliases", - operationId: "PleromaAPI.AccountController.add_aliases", - requestBody: request_body("Parameters", alias_request(), required: true), - security: [%{"oAuth" => ["write:accounts"]}], - responses: %{ - 200 => Operation.response("Account", "application/json", Account), - 403 => Operation.response("Forbidden", "application/json", ApiError) - } - } - end - - def delete_aliases_operation do - %Operation{ - tags: ["Accounts"], - summary: "Delete ActivityPub aliases", - operationId: "PleromaAPI.AccountController.delete_aliases", - requestBody: request_body("Parameters", alias_request(), required: true), - security: [%{"oAuth" => ["write:accounts"]}], - responses: %{ - 200 => Operation.response("Account", "application/json", Account) - } - } - end - defp id_param do Operation.parameter(:id, :path, FlakeID, "Account ID", example: "9umDrYheeY451cQnEe", required: true ) end - - defp alias_request do - %Schema{ - title: "AccountAliasRequest", - description: "POST body for adding/deleting AP aliases", - type: :object, - properties: %{ - aliases: %Schema{ - type: :array, - items: %Schema{type: :string} - } - }, - example: %{ - "aliases" => ["https://beepboop.social/users/beep", "https://mushroom.kingdom/users/toad"] - } - } - end end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 4fd27edf5..cf743932c 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do type: :object, properties: %{ ap_id: %Schema{type: :string}, - ap_aliases: %Schema{type: :array, items: %Schema{type: :string}}, + also_known_as: %Schema{type: :array, items: %Schema{type: :string}}, allow_following_move: %Schema{ type: :boolean, description: "whether the user allows automatically follow moved following accounts" diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index f45678184..b0ec97d87 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -186,6 +186,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p :show_role, :skip_thread_containment, :allow_following_move, + :also_known_as, :discoverable, :accepts_chat_messages ] @@ -210,6 +211,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p if bot, do: {:ok, "Service"}, else: {:ok, "Person"} end) |> Maps.put_if_present(:actor_type, params[:actor_type]) + |> Maps.put_if_present(:also_known_as, params[:also_known_as]) # What happens here: # diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 4f29a80fb..f78b04565 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -267,7 +267,7 @@ defp do_render("show.json", %{user: user} = opts) do # Pleroma extension pleroma: %{ ap_id: user.ap_id, - ap_aliases: user.ap_aliases, + also_known_as: user.also_known_as, confirmation_pending: user.confirmation_pending, tags: user.tags, hide_followers_count: user.hide_followers_count, diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex index 03e5781a3..563edded7 100644 --- a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex @@ -39,11 +39,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites ) - plug( - OAuthScopesPlug, - %{scopes: ["write:accounts"]} when action in [:add_aliases, :delete_aliases] - ) - plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend) plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) @@ -112,24 +107,4 @@ def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, {:error, message} -> json_response(conn, :forbidden, %{error: message}) end end - - @doc "POST /api/v1/pleroma/accounts/ap_aliases" - def add_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params) - when is_list(aliases) do - with {:ok, user} <- User.add_aliases(user, aliases) do - render(conn, "show.json", user: user) - else - {:error, message} -> json_response(conn, :forbidden, %{error: message}) - end - end - - @doc "DELETE /api/v1/pleroma/accounts/ap_aliases" - def delete_aliases(%{assigns: %{user: user}, body_params: %{aliases: aliases}} = conn, _params) - when is_list(aliases) do - with {:ok, user} <- User.delete_aliases(user, aliases) do - render(conn, "show.json", user: user) - else - {:error, message} -> json_response(conn, :forbidden, %{error: message}) - end - end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index fbab0fc27..c6433cc53 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -345,9 +345,6 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/subscribe", AccountController, :subscribe) post("/accounts/:id/unsubscribe", AccountController, :unsubscribe) - - post("/accounts/ap_aliases", AccountController, :add_aliases) - delete("/accounts/ap_aliases", AccountController, :delete_aliases) end post("/accounts/confirmation_resend", AccountController, :confirmation_resend) diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index fb142ce8d..b0df356a3 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -59,10 +59,7 @@ defp gather_links(%User{} = user) do end defp gather_aliases(%User{} = user) do - user.ap_aliases - |> MapSet.new() - |> MapSet.put(user.ap_id) - |> MapSet.to_list() + [user.ap_id] ++ user.also_known_as end def represent_user(user, "JSON") do @@ -78,6 +75,10 @@ def represent_user(user, "JSON") do def represent_user(user, "XML") do {:ok, user} = User.ensure_keys_present(user) + aliases = + gather_aliases(user) + |> Enum.map(fn the_alias -> {:Alias, the_alias} end) + links = gather_links(user) |> Enum.map(fn link -> {:Link, link} end) @@ -86,9 +87,8 @@ def represent_user(user, "XML") do :XRD, %{xmlns: "http://docs.oasis-open.org/ns/xri/xrd-1.0"}, [ - {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"}, - {:Alias, user.ap_id} - ] ++ links + {:Subject, "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}"} + ] ++ aliases ++ links } |> XmlBuilder.to_doc() end diff --git a/priv/repo/migrations/20200717025041_add_aliases_to_users.exs b/priv/repo/migrations/20200717025041_add_aliases_to_users.exs deleted file mode 100644 index a6ace6e0f..000000000 --- a/priv/repo/migrations/20200717025041_add_aliases_to_users.exs +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Pleroma.Repo.Migrations.AddAliasesToUsers do - use Ecto.Migration - - def change do - alter table(:users) do - add(:ap_aliases, {:array, :string}, default: []) - end - end -end diff --git a/test/user_test.exs b/test/user_test.exs index 941e48408..b47405895 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -2024,48 +2024,4 @@ test "avatar fallback" do assert User.avatar_url(user, no_default: true) == nil end - - test "add_aliases/2" do - user = insert(:user) - - aliases = [ - "https://gleasonator.com/users/alex", - "https://gleasonator.com/users/alex", - "https://animalliberation.social/users/alex" - ] - - {:ok, user} = User.add_aliases(user, aliases) - - assert user.ap_aliases == [ - "https://animalliberation.social/users/alex", - "https://gleasonator.com/users/alex" - ] - end - - test "add_aliases/2 with invalid alias" do - user = insert(:user) - {:error, _} = User.add_aliases(user, ["invalid_alias"]) - {:error, _} = User.add_aliases(user, ["http://still_invalid"]) - {:error, _} = User.add_aliases(user, ["http://validalias.com/users/dude", "invalid_alias"]) - end - - test "delete_aliases/2" do - user = - insert(:user, - ap_aliases: [ - "https://animalliberation.social/users/alex", - "https://benis.social/users/benis", - "https://gleasonator.com/users/alex" - ] - ) - - aliases = ["https://benis.social/users/benis"] - - {:ok, user} = User.delete_aliases(user, aliases) - - assert user.ap_aliases == [ - "https://animalliberation.social/users/alex", - "https://gleasonator.com/users/alex" - ] - end end diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index b888e4c71..467110f3b 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -216,6 +216,16 @@ test "updates the user's name", %{conn: conn} do assert user_data["display_name"] == "markorepairs" end + test "updates the user's AKAs", %{conn: conn} do + conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "also_known_as" => ["https://mushroom.kingdom/users/mario"] + }) + + assert user_data = json_response_and_validate_schema(conn, 200) + assert user_data["pleroma"]["also_known_as"] == ["https://mushroom.kingdom/users/mario"] + end + test "updates the user's avatar", %{user: user, conn: conn} do new_avatar = %Plug.Upload{ content_type: "image/jpg", diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index a55b5a06e..bbf7b33a8 100644 --- a/test/web/mastodon_api/views/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -38,7 +38,7 @@ test "Represent a user account" do inserted_at: ~N[2017-08-15 15:47:06.597036], emoji: %{"karjalanpiirakka" => "/file.png"}, raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"", - ap_aliases: ["https://shitposter.zone/users/shp"] + also_known_as: ["https://shitposter.zone/users/shp"] }) expected = %{ @@ -78,7 +78,7 @@ test "Represent a user account" do }, pleroma: %{ ap_id: user.ap_id, - ap_aliases: ["https://shitposter.zone/users/shp"], + also_known_as: ["https://shitposter.zone/users/shp"], background_image: "https://example.com/images/asuka_hospital.png", favicon: "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png", @@ -174,7 +174,7 @@ test "Represent a Service(bot) account" do }, pleroma: %{ ap_id: user.ap_id, - ap_aliases: [], + also_known_as: [], background_image: nil, favicon: "https://shitposter.club/plugins/Qvitter/img/gnusocial-favicons/favicon-16x16.png", diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs index da01a8218..07909d48b 100644 --- a/test/web/pleroma_api/controllers/account_controller_test.exs +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -281,33 +281,4 @@ test "returns 404 when subscription_target not found" do assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn, 404) end end - - describe "aliases controllers" do - setup do: oauth_access(["write:accounts"]) - - test "adds aliases", %{conn: conn} do - aliases = ["https://gleasonator.com/users/alex"] - - conn = - conn - |> put_req_header("content-type", "application/json") - |> post("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases}) - - assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200) - assert Enum.count(res) == 1 - end - - test "deletes aliases", %{conn: conn, user: user} do - aliases = ["https://gleasonator.com/users/alex"] - User.add_aliases(user, aliases) - - conn = - conn - |> put_req_header("content-type", "application/json") - |> delete("/api/v1/pleroma/accounts/ap_aliases", %{"aliases" => aliases}) - - assert %{"pleroma" => %{"ap_aliases" => res}} = json_response_and_validate_schema(conn, 200) - assert Enum.count(res) == 0 - end - end end diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs index 50b6c4b3e..ce9eb0650 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -33,7 +33,7 @@ test "Webfinger JRD" do user = insert(:user, ap_id: "https://hyrule.world/users/zelda", - ap_aliases: ["https://mushroom.kingdom/users/toad"] + also_known_as: ["https://mushroom.kingdom/users/toad"] ) response = @@ -61,14 +61,20 @@ test "it returns 404 when user isn't found (JSON)" do end test "Webfinger XML" do - user = insert(:user) + user = + insert(:user, + ap_id: "https://hyrule.world/users/zelda", + also_known_as: ["https://mushroom.kingdom/users/toad"] + ) response = build_conn() |> put_req_header("accept", "application/xrd+xml") |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") + |> response(200) - assert response(response, 200) + assert response =~ "https://hyrule.world/users/zelda" + assert response =~ "https://mushroom.kingdom/users/toad" end test "it returns 404 when user isn't found (XML)" do From a3964b373e696301dae0e432983f55d22f055c5f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 8 Oct 2020 15:46:03 -0500 Subject: [PATCH 04/14] Aliases: move changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97908caf7..6107ac07e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`) - Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`) - Mix task option for force-unfollowing relays +- Ability to set ActivityPub aliases for follower migration. ### Changed @@ -182,7 +183,6 @@ switched to a new configuration mechanism, however it was not officially removed - Support pagination in emoji packs API (for packs and for files in pack) - Support for viewing instances favicons next to posts and accounts - Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata. -- Ability to set ActivityPub aliases for follower migration. - "By approval" registrations mode. - Configuration: Added `:welcome` settings for the welcome message to newly registered users. You can send a welcome message as a direct message, chat or email. - Ability to hide favourites and emoji reactions in the API with `[:instance, :show_reactions]` config. From 5ec7d88b77360ed78f75be6b1f94895c3f602972 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 8 Oct 2020 16:33:47 -0500 Subject: [PATCH 05/14] Aliases: fix URL regex --- lib/pleroma/user.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e0252c8ee..d66c92b14 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -51,8 +51,7 @@ defmodule Pleroma.User do # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength - @url_regex ~r/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/=]*)/ + @url_regex ~r/^https?:\/\/[^\s]{1,256}$/ @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ From 744b34709db9c11767a9bc57fe0bb21c96e826c7 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Wed, 30 Dec 2020 14:22:48 -0600 Subject: [PATCH 06/14] Do not reverse order of reports. We want newest ones sorted to the top. --- lib/pleroma/web/admin_api/views/report_view.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 535556370..da949e306 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -19,8 +19,7 @@ def render("index.json", %{reports: reports}) do reports: reports[:items] |> Enum.map(&Report.extract_report_info/1) - |> Enum.map(&render(__MODULE__, "show.json", &1)) - |> Enum.reverse(), + |> Enum.map(&render(__MODULE__, "show.json", &1)), total: reports[:total] } end From 11d40e92b7ee4d855c02d24a485035fb622a138a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 30 Dec 2020 18:53:27 -0600 Subject: [PATCH 07/14] Render AKAs in Actor endpoints --- lib/pleroma/web/activity_pub/views/user_view.ex | 3 ++- test/pleroma/web/activity_pub/views/user_view_test.exs | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 93c9f436c..241224b57 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -112,7 +112,8 @@ def render("user.json", %{user: user}) do "tag" => emoji_tags, # Note: key name is indeed "discoverable" (not an error) "discoverable" => user.is_discoverable, - "capabilities" => capabilities + "capabilities" => capabilities, + "alsoKnownAs" => user.also_known_as } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index fe6ddf0d6..5702c1b6f 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -80,6 +80,12 @@ test "renders an invisible user with the invisible property set to true" do assert %{"invisible" => true} = UserView.render("service.json", %{user: user}) end + test "renders AKAs" do + akas = ["https://i.tusooa.xyz/users/test-pleroma"] + user = insert(:user, also_known_as: akas) + assert %{"alsoKnownAs" => ^akas} = UserView.render("user.json", %{user: user}) + end + describe "endpoints" do test "local users have a usable endpoints structure" do user = insert(:user) From e4791258d4483cd9dad6016ec453e6ca7ea10d73 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Dec 2020 10:53:18 -0600 Subject: [PATCH 08/14] Ensure newest report is returned first in the list --- .../web/admin_api/views/report_view_test.exs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/pleroma/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs index ff3453208..3914751b5 100644 --- a/test/pleroma/web/admin_api/views/report_view_test.exs +++ b/test/pleroma/web/admin_api/views/report_view_test.exs @@ -143,4 +143,29 @@ test "doesn't error out when the user doesn't exists" do assert %{} = ReportView.render("show.json", Report.extract_report_info(activity)) end + + test "reports are ordered newest first" do + user = insert(:user) + other_user = insert(:user) + + {:ok, report1} = + CommonAPI.report(user, %{ + account_id: other_user.id, + comment: "first report" + }) + + {:ok, report2} = + CommonAPI.report(user, %{ + account_id: other_user.id, + comment: "second report" + }) + + %{reports: rendered} = + ReportView.render("index.json", + reports: Pleroma.Web.ActivityPub.Utils.get_reports(%{}, 1, 50) + ) + + assert report2.id == rendered |> Enum.at(0) |> Map.get(:id) + assert report1.id == rendered |> Enum.at(1) |> Map.get(:id) + end end From 0d6b9ce8ca5afd1ddaa0af4061fd956b29d17826 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 31 Dec 2020 18:51:57 +0000 Subject: [PATCH 09/14] Apply 2 suggestion(s) to 1 file(s) --- lib/pleroma/web/web_finger.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index 7c009388a..a109e1acc 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -59,7 +59,7 @@ defp gather_links(%User{} = user) do end defp gather_aliases(%User{} = user) do - [user.ap_id] ++ user.also_known_as + [user.ap_id | user.also_known_as] end def represent_user(user, "JSON") do @@ -76,8 +76,9 @@ def represent_user(user, "XML") do {:ok, user} = User.ensure_keys_present(user) aliases = - gather_aliases(user) - |> Enum.map(fn the_alias -> {:Alias, the_alias} end) + user + |> gather_aliases() + |> Enum.map(&{:Alias, &1}) links = gather_links(user) From 4200a063408966b1e14e79b18c96ce59af23ec35 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 31 Dec 2020 12:53:28 -0600 Subject: [PATCH 10/14] Aliases: refactor validate_also_known_as/1 --- lib/pleroma/user.ex | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7b26ac7a3..230845662 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -2458,9 +2458,10 @@ def sanitize_html(%User{} = user, filter) do defp validate_also_known_as(changeset) do validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as -> - case Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do - true -> [] - false -> [also_known_as: "Invalid ap_id format. Must be a URL."] + if Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do + [] + else + [also_known_as: "Invalid ap_id format. Must be a URL."] end end) end From 83d97ab98e99a16d0ba25a57df1be182dfb9b938 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Thu, 31 Dec 2020 13:15:44 -0600 Subject: [PATCH 11/14] Document reports ordering change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1604ab3a..77959c415 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm` - Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries. - Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators. +- Admin API: Reports now ordered by newest ### Added From 0ec7e9b8e98f6310f19d0b0a6ad82fc9c70a9d47 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Fri, 1 Jan 2021 11:58:42 -0600 Subject: [PATCH 12/14] AdminAPI: return id for moderation log entries --- docs/API/admin_api.md | 1 + lib/pleroma/web/admin_api/views/moderation_log_view.ex | 1 + .../pleroma/web/admin_api/views/moderation_log_view_test.exs | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md index 266f8cef8..5253dc668 100644 --- a/docs/API/admin_api.md +++ b/docs/API/admin_api.md @@ -1123,6 +1123,7 @@ Loads json generated from `config/descriptions.exs`. ```json [ { + "id": 1234, "data": { "actor": { "id": 1, diff --git a/lib/pleroma/web/admin_api/views/moderation_log_view.ex b/lib/pleroma/web/admin_api/views/moderation_log_view.ex index 112f9e0e1..3fa778b0a 100644 --- a/lib/pleroma/web/admin_api/views/moderation_log_view.ex +++ b/lib/pleroma/web/admin_api/views/moderation_log_view.ex @@ -21,6 +21,7 @@ def render("show.json", %{log_entry: log_entry}) do |> DateTime.to_unix() %{ + id: log_entry.id, data: log_entry.data, time: time, message: ModerationLog.get_log_entry_message(log_entry) diff --git a/test/pleroma/web/admin_api/views/moderation_log_view_test.exs b/test/pleroma/web/admin_api/views/moderation_log_view_test.exs index 390d7bbeb..a4748990e 100644 --- a/test/pleroma/web/admin_api/views/moderation_log_view_test.exs +++ b/test/pleroma/web/admin_api/views/moderation_log_view_test.exs @@ -9,6 +9,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do describe "renders `report_note_delete` log messages" do setup do log1 = %Pleroma.ModerationLog{ + id: 1, data: %{ "action" => "report_note_delete", "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, @@ -21,6 +22,7 @@ defmodule Pleroma.Web.AdminAPI.ModerationLogViewTest do } log2 = %Pleroma.ModerationLog{ + id: 2, data: %{ "action" => "report_note_delete", "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, @@ -42,6 +44,7 @@ test "renders `report_note_delete` log messages", %{log1: log1, log2: log2} do ) == %{ items: [ %{ + id: 1, data: %{ "action" => "report_note_delete", "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, @@ -59,6 +62,7 @@ test "renders `report_note_delete` log messages", %{log1: log1, log2: log2} do time: 1_605_622_400 }, %{ + id: 2, data: %{ "action" => "report_note_delete", "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, @@ -82,6 +86,7 @@ test "renders `report_note_delete` log messages", %{log1: log1, log2: log2} do test "renders `report_note_delete` log message", %{log1: log} do assert ModerationLogView.render("show.json", %{log_entry: log}) == %{ + id: 1, data: %{ "action" => "report_note_delete", "actor" => %{"id" => "A1I7G8", "nickname" => "admin", "type" => "user"}, From 8e5904daa59f9e7c85e1431605067b86506bcfc9 Mon Sep 17 00:00:00 2001 From: lain Date: Mon, 4 Jan 2021 18:40:59 +0100 Subject: [PATCH 13/14] SideEffects.DeleteTest: asyncify. Replace Mock with Mox, mock out Logger. --- config/test.exs | 4 + lib/pleroma/logging.ex | 7 + lib/pleroma/web/activity_pub/activity_pub.ex | 5 + .../activity_pub/activity_pub/streaming.ex | 12 ++ lib/pleroma/web/activity_pub/side_effects.ex | 8 +- .../activity_pub/side_effects/delete_test.exs | 147 ++++++++++++++++++ .../web/activity_pub/side_effects_test.exs | 110 ------------- test/support/conn_case.ex | 2 + test/support/data_case.ex | 2 + test/support/mocks.ex | 7 +- 10 files changed, 190 insertions(+), 114 deletions(-) create mode 100644 lib/pleroma/logging.ex create mode 100644 lib/pleroma/web/activity_pub/activity_pub/streaming.ex create mode 100644 test/pleroma/web/activity_pub/side_effects/delete_test.exs diff --git a/config/test.exs b/config/test.exs index a85881592..7fc457463 100644 --- a/config/test.exs +++ b/config/test.exs @@ -134,6 +134,10 @@ config :pleroma, :cachex, provider: Pleroma.CachexMock +config :pleroma, :side_effects, + ap_streamer: Pleroma.Web.ActivityPub.ActivityPubMock, + logger: Pleroma.LoggerMock + if File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" else diff --git a/lib/pleroma/logging.ex b/lib/pleroma/logging.ex new file mode 100644 index 000000000..37b201c29 --- /dev/null +++ b/lib/pleroma/logging.ex @@ -0,0 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Logging do + @callback error(String.t()) :: any() +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 15f298bb8..3e346d49a 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -33,6 +33,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do require Pleroma.Constants @behaviour Pleroma.Web.ActivityPub.ActivityPub.Persisting + @behaviour Pleroma.Web.ActivityPub.ActivityPub.Streaming defp get_recipients(%{"type" => "Create"} = data) do to = Map.get(data, "to", []) @@ -224,6 +225,7 @@ def stream_out_participations(participations) do Streamer.stream("participation", participations) end + @impl true def stream_out_participations(%Object{data: %{"context" => context}}, user) do with %Conversation{} = conversation <- Conversation.get_for_ap_id(context) do conversation = Repo.preload(conversation, :participations) @@ -240,8 +242,10 @@ def stream_out_participations(%Object{data: %{"context" => context}}, user) do end end + @impl true def stream_out_participations(_, _), do: :noop + @impl true def stream_out(%Activity{data: %{"type" => data_type}} = activity) when data_type in ["Create", "Announce", "Delete"] do activity @@ -249,6 +253,7 @@ def stream_out(%Activity{data: %{"type" => data_type}} = activity) |> Streamer.stream(activity) end + @impl true def stream_out(_activity) do :noop end diff --git a/lib/pleroma/web/activity_pub/activity_pub/streaming.ex b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex new file mode 100644 index 000000000..30009f2fb --- /dev/null +++ b/lib/pleroma/web/activity_pub/activity_pub/streaming.ex @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ActivityPub.Streaming do + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User + + @callback stream_out(Activity.t()) :: any() + @callback stream_out_participations(Object.t(), User.t()) :: any() +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 55c99ad0c..e37caf6a0 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -28,6 +28,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do require Logger @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @ap_streamer Pleroma.Config.get([:side_effects, :ap_streamer], ActivityPub) + @logger Pleroma.Config.get([:side_effects, :logger], Logger) @behaviour Pleroma.Web.ActivityPub.SideEffects.Handling @@ -287,12 +289,12 @@ def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, MessageReference.delete_for_object(deleted_object) - ActivityPub.stream_out(object) - ActivityPub.stream_out_participations(deleted_object, user) + @ap_streamer.stream_out(object) + @ap_streamer.stream_out_participations(deleted_object, user) :ok else {:actor, _} -> - Logger.error("The object doesn't have an actor: #{inspect(deleted_object)}") + @logger.error("The object doesn't have an actor: #{inspect(deleted_object)}") :no_object_actor end diff --git a/test/pleroma/web/activity_pub/side_effects/delete_test.exs b/test/pleroma/web/activity_pub/side_effects/delete_test.exs new file mode 100644 index 000000000..e4ad606a9 --- /dev/null +++ b/test/pleroma/web/activity_pub/side_effects/delete_test.exs @@ -0,0 +1,147 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.SideEffects.DeleteTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase, async: true + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.SideEffects + alias Pleroma.Web.CommonAPI + + alias Pleroma.LoggerMock + alias Pleroma.Web.ActivityPub.ActivityPubMock + + import Mox + import Pleroma.Factory + + describe "user deletion" do + setup do + user = insert(:user) + + {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) + {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) + + %{ + user: user, + delete_user: delete_user + } + end + + test "it handles user deletions", %{delete_user: delete, user: user} do + {:ok, _delete, _} = SideEffects.handle(delete) + ObanHelpers.perform_all() + + assert User.get_cached_by_ap_id(user.ap_id).deactivated + end + end + + describe "object deletion" do + setup do + user = insert(:user) + other_user = insert(:user) + + {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"}) + {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op}) + {:ok, favorite} = CommonAPI.favorite(user, post.id) + object = Object.normalize(post) + {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) + {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) + + %{ + user: user, + delete: delete, + post: post, + object: object, + op: op, + favorite: favorite + } + end + + test "it handles object deletions", %{ + delete: delete, + post: post, + object: object, + user: user, + op: op, + favorite: favorite + } do + object_id = object.id + user_id = user.id + + ActivityPubMock + |> expect(:stream_out, fn ^delete -> nil end) + |> expect(:stream_out_participations, fn %Object{id: ^object_id}, %User{id: ^user_id} -> + nil + end) + + {:ok, _delete, _} = SideEffects.handle(delete) + user = User.get_cached_by_ap_id(object.data["actor"]) + + object = Object.get_by_id(object.id) + assert object.data["type"] == "Tombstone" + refute Activity.get_by_id(post.id) + refute Activity.get_by_id(favorite.id) + + user = User.get_by_id(user.id) + assert user.note_count == 0 + + object = Object.normalize(op.data["object"], false) + + assert object.data["repliesCount"] == 0 + end + + test "it handles object deletions when the object itself has been pruned", %{ + delete: delete, + post: post, + object: object, + user: user, + op: op + } do + object_id = object.id + user_id = user.id + + ActivityPubMock + |> expect(:stream_out, fn ^delete -> nil end) + |> expect(:stream_out_participations, fn %Object{id: ^object_id}, %User{id: ^user_id} -> + nil + end) + + {:ok, _delete, _} = SideEffects.handle(delete) + user = User.get_cached_by_ap_id(object.data["actor"]) + + object = Object.get_by_id(object.id) + assert object.data["type"] == "Tombstone" + refute Activity.get_by_id(post.id) + + user = User.get_by_id(user.id) + assert user.note_count == 0 + + object = Object.normalize(op.data["object"], false) + + assert object.data["repliesCount"] == 0 + end + + test "it logs issues with objects deletion", %{ + delete: delete, + object: object + } do + {:ok, _object} = + object + |> Object.change(%{data: Map.delete(object.data, "actor")}) + |> Repo.update() + + LoggerMock + |> expect(:error, fn str -> assert str =~ "The object doesn't have an actor" end) + + {:error, :no_object_actor} = SideEffects.handle(delete) + end + end +end diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 297fc0b84..50af7a507 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -19,7 +19,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do alias Pleroma.Web.ActivityPub.SideEffects alias Pleroma.Web.CommonAPI - import ExUnit.CaptureLog import Mock import Pleroma.Factory @@ -131,115 +130,6 @@ test "it uses a given changeset to update", %{user: user, update: update} do end end - describe "delete objects" do - setup do - user = insert(:user) - other_user = insert(:user) - - {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"}) - {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op}) - {:ok, favorite} = CommonAPI.favorite(user, post.id) - object = Object.normalize(post) - {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) - {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) - {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) - {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) - - %{ - user: user, - delete: delete, - post: post, - object: object, - delete_user: delete_user, - op: op, - favorite: favorite - } - end - - test "it handles object deletions", %{ - delete: delete, - post: post, - object: object, - user: user, - op: op, - favorite: favorite - } do - with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], - stream_out: fn _ -> nil end, - stream_out_participations: fn _, _ -> nil end do - {:ok, delete, _} = SideEffects.handle(delete) - user = User.get_cached_by_ap_id(object.data["actor"]) - - assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) - assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) - end - - object = Object.get_by_id(object.id) - assert object.data["type"] == "Tombstone" - refute Activity.get_by_id(post.id) - refute Activity.get_by_id(favorite.id) - - user = User.get_by_id(user.id) - assert user.note_count == 0 - - object = Object.normalize(op.data["object"], false) - - assert object.data["repliesCount"] == 0 - end - - test "it handles object deletions when the object itself has been pruned", %{ - delete: delete, - post: post, - object: object, - user: user, - op: op - } do - with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], - stream_out: fn _ -> nil end, - stream_out_participations: fn _, _ -> nil end do - {:ok, delete, _} = SideEffects.handle(delete) - user = User.get_cached_by_ap_id(object.data["actor"]) - - assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) - assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) - end - - object = Object.get_by_id(object.id) - assert object.data["type"] == "Tombstone" - refute Activity.get_by_id(post.id) - - user = User.get_by_id(user.id) - assert user.note_count == 0 - - object = Object.normalize(op.data["object"], false) - - assert object.data["repliesCount"] == 0 - end - - test "it handles user deletions", %{delete_user: delete, user: user} do - {:ok, _delete, _} = SideEffects.handle(delete) - ObanHelpers.perform_all() - - assert User.get_cached_by_ap_id(user.ap_id).deactivated - end - - test "it logs issues with objects deletion", %{ - delete: delete, - object: object - } do - {:ok, object} = - object - |> Object.change(%{data: Map.delete(object.data, "actor")}) - |> Repo.update() - - Object.invalid_object_cache(object) - - assert capture_log(fn -> - {:error, :no_object_actor} = SideEffects.handle(delete) - end) =~ "object doesn't have an actor" - end - end - describe "EmojiReact objects" do setup do poster = insert(:user) diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 02f49c590..f20e3d955 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -138,6 +138,8 @@ defp json_response_and_validate_schema(conn, _status) do Pleroma.DataCase.stub_pipeline() + Mox.verify_on_exit!() + {:ok, conn: Phoenix.ConnTest.build_conn()} end end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 5c657c1d9..0b41f0f63 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -85,6 +85,8 @@ def clear_cachex do stub_pipeline() + Mox.verify_on_exit!() + :ok end diff --git a/test/support/mocks.ex b/test/support/mocks.ex index a600a6458..442ff5b71 100644 --- a/test/support/mocks.ex +++ b/test/support/mocks.ex @@ -13,7 +13,10 @@ ) Mox.defmock(Pleroma.Web.ActivityPub.ActivityPubMock, - for: Pleroma.Web.ActivityPub.ActivityPub.Persisting + for: [ + Pleroma.Web.ActivityPub.ActivityPub.Persisting, + Pleroma.Web.ActivityPub.ActivityPub.Streaming + ] ) Mox.defmock(Pleroma.Web.ActivityPub.SideEffectsMock, @@ -23,3 +26,5 @@ Mox.defmock(Pleroma.Web.FederatorMock, for: Pleroma.Web.Federator.Publishing) Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting) + +Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging) From e802b48d558ccd4a65a6da2bcc6dacb057b7fd09 Mon Sep 17 00:00:00 2001 From: lain Date: Tue, 5 Jan 2021 13:10:14 +0100 Subject: [PATCH 14/14] User: Use ObjectID type to validate also-known-as field --- lib/pleroma/user.ex | 14 +------------- .../web/mastodon_api/update_credentials_test.exs | 9 +++++++++ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 230845662..52730fd8d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -51,7 +51,6 @@ defmodule Pleroma.User do # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - @url_regex ~r/^https?:\/\/[^\s]{1,256}$/ @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ @@ -143,7 +142,7 @@ defmodule Pleroma.User do field(:allow_following_move, :boolean, default: true) field(:skip_thread_containment, :boolean, default: false) field(:actor_type, :string, default: "Person") - field(:also_known_as, {:array, :string}, default: []) + field(:also_known_as, {:array, ObjectValidators.ObjectID}, default: []) field(:inbox, :string) field(:shared_inbox, :string) field(:accepts_chat_messages, :boolean, default: nil) @@ -530,7 +529,6 @@ def update_changeset(struct, params \\ %{}) do ) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) - |> validate_also_known_as() |> validate_length(:bio, max: bio_limit) |> validate_length(:name, min: 1, max: name_limit) |> validate_inclusion(:actor_type, ["Person", "Service"]) @@ -2456,16 +2454,6 @@ def sanitize_html(%User{} = user, filter) do |> Map.put(:fields, fields) end - defp validate_also_known_as(changeset) do - validate_change(changeset, :also_known_as, fn :also_known_as, also_known_as -> - if Enum.all?(also_known_as, fn a -> Regex.match?(@url_regex, a) end) do - [] - else - [also_known_as: "Invalid ap_id format. Must be a URL."] - end - end) - end - def get_host(%User{ap_id: ap_id} = _user) do URI.parse(ap_id).host end diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index ff0147244..e3e437a19 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -228,6 +228,15 @@ test "updates the user's AKAs", %{conn: conn} do assert user_data["pleroma"]["also_known_as"] == ["https://mushroom.kingdom/users/mario"] end + test "doesn't update non-url akas", %{conn: conn} do + conn = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "also_known_as" => ["aReallyCoolGuy"] + }) + + assert json_response_and_validate_schema(conn, 403) + end + test "updates the user's avatar", %{user: user, conn: conn} do new_avatar = %Plug.Upload{ content_type: "image/jpeg",