Fix leaking private configuration parameters in Mastodon and Twitter APIs, and add...
authorrinpatch <rinpatch@sdf.org>
Wed, 24 Apr 2019 17:01:42 +0000 (20:01 +0300)
committerrinpatch <rinpatch@sdf.org>
Wed, 24 Apr 2019 17:01:42 +0000 (20:01 +0300)
This patch:
- Fixes `rights` in twitterapi ignoring `show_role`
- Fixes exposing default scope of the user to anyone in Mastodon API
- Extends Mastodon API to be able to show and set `no_rich_text`, `default_scope`, `hide_follows`, `hide_followers`, `hide_favorites` (requested by the FE in #674)

Sorry in advance for 500 line one commit diff, I should have split it up to separate MRs

CHANGELOG.md
docs/api/differences_in_mastoapi_responses.md
lib/pleroma/user/info.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/twitter_api/views/user_view.ex
test/web/mastodon_api/account_view_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs
test/web/twitter_api/views/user_view_test.exs

index 70381f3827a854b2fa4f74bb4e12aa1fefe1da4c..51ba239b6e9e0b5db621bd56aea429037154e20b 100644 (file)
@@ -44,7 +44,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
 - Mastodon API: Provide plaintext versions of cw/content in the Status entity
 - Mastodon API: Add `pleroma.conversation_id`, `pleroma.in_reply_to_account_acct` fields to the Status entity
-- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending` fields to the User entity
+- Mastodon API: Add `pleroma.tags`, `pleroma.relationship{}`, `pleroma.is_moderator`, `pleroma.is_admin`, `pleroma.confirmation_pending`, `pleroma.hide_followers`, `pleroma.hide_follows`, `pleroma.hide_favorites` fields to the User entity
+- Mastodon API: Add `pleroma.show_role`, `pleroma.no_rich_text` fields to the User entity (when the user is requesting themselves)
+- Mastodon API: Add support for updating `no_rich_text`, `hide_followers`, `hide_follows`, `hide_favorites`, `show_role` in `PATCH /api/v1/update_credentials`
 - Mastodon API: Add `pleroma.is_seen` to the Notification entity
 - Mastodon API: Add `pleroma.local` to the Status entity
 - Mastodon API: Add `preview` parameter to `POST /api/v1/statuses`
@@ -72,12 +74,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 - MediaProxy: S3 link encoding
 - Rich Media: Reject any data which cannot be explicitly encoded into JSON
 - Pleroma API: Importing follows from Mastodon 2.8+
+- Twitter API: Exposing default scope, `no_rich_text` of the user to anyone
+- Twitter API: Returning the `role` object in user entity despite `show_role = false`
 - Mastodon API: `/api/v1/favourites` serving only public activities
 - Mastodon API: Reblogs having `in_reply_to_id` - `null` even when they are replies
 - Mastodon API: Streaming API broadcasting wrong activity id
 - Mastodon API: 500 errors when requesting a card for a private conversation
 - Mastodon API: Handling of `reblogs` in `/api/v1/accounts/:id/follow`
 - Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
+- Mastodon API: Exposing default scope of the user to anyone
 
 ## [0.9.9999] - 2019-04-05
 ### Security
index 3bb1bd41f7cb46d071c1e891ef97453f47b7453a..7f05527fb52b3123226932dcf97dd5c286d612b4 100644 (file)
@@ -38,9 +38,12 @@ Has these additional fields under the `pleroma` object:
 
 - `tags`: Lists an array of tags for the user
 - `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/api/entities/#relationship
-- `is_moderator`: boolean, true if user is a moderator
-- `is_admin`: boolean, true if user is an admin
+- `is_moderator`: boolean, nullable,  true if user is a moderator
+- `is_admin`: boolean, nullable, true if user is an admin
 - `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
+- `hide_followers`: boolean, true when the user has follower hiding enabled
+- `hide_follows`: boolean, true when the user has follow hiding enabled
+- `show_role`: boolean, nullable (only shown when the user is requesting themselves), true when the user wants his role (e.g admin, moderator) to be shown
 
 ## Account Search
 
@@ -60,3 +63,13 @@ Additional parameters can be added to the JSON body/Form data:
 
 - `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example.
 - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint.
+
+## PATCH `/api/v1/update_credentials`
+
+Additional parameters can be added to the JSON body/Form data:
+
+- `no_rich_text` - if true, html tags are stripped from all statuses requested from the API
+- `hide_followers` - if true, user's followers will be hidden
+- `hide_follows` - if true, user's follows will be hidden
+- `hide_favorites` - if true, user's favorites timeline will be hidden
+- `show_role` - if true, user's role (e.g admin, moderator) will be exposed to anyone in the API
index 7f22a45b5c7116b4af4a86201e2c8396137bea1a..a3658d57ff495cd65a8fe1e9bb45412318d66072 100644 (file)
@@ -227,14 +227,6 @@ defmodule Pleroma.User.Info do
     cast(info, params, [:confirmation_pending, :confirmation_token])
   end
 
-  def mastodon_profile_update(info, params) do
-    info
-    |> cast(params, [
-      :locked,
-      :banner
-    ])
-  end
-
   def mastodon_settings_update(info, settings) do
     params = %{settings: settings}
 
index 0ba8d9eea71ea082a59586de39b37ad51c99e19e..1379baacf62c5bc922bdabafdc804774fb450dbd 100644 (file)
@@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   alias Pleroma.Web.OAuth.Authorization
   alias Pleroma.Web.OAuth.Token
 
-  import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2]
+  alias Pleroma.Web.ControllerHelper
   import Ecto.Query
 
   require Logger
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
   action_fallback(:errors)
 
   def create_app(conn, params) do
-    scopes = oauth_scopes(params, ["read"])
+    scopes = ControllerHelper.oauth_scopes(params, ["read"])
 
     app_attrs =
       params
@@ -96,8 +96,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
       end)
 
     info_params =
-      %{}
-      |> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end)
+      [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role]
+      |> Enum.reduce(%{}, fn key, acc ->
+        add_if_present(acc, params, to_string(key), key, fn value ->
+          {:ok, ControllerHelper.truthy_param?(value)}
+        end)
+      end)
       |> add_if_present(params, "header", :banner, fn value ->
         with %Plug.Upload{} <- value,
              {:ok, object} <- ActivityPub.upload(value, type: :banner) do
@@ -107,7 +111,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
         end
       end)
 
-    info_cng = User.Info.mastodon_profile_update(user.info, info_params)
+    info_cng = User.Info.profile_update(user.info, info_params)
 
     with changeset <- User.update_changeset(user, user_params),
          changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
index d87fdb15dac466484f0878ce17137804aa47ffdc..6e6f0ba9304a97f9257156f5cc3907d7e9be592d 100644 (file)
@@ -113,21 +113,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       bot: bot,
       source: %{
         note: "",
-        privacy: user_info.default_scope,
         sensitive: false
       },
 
       # Pleroma extension
-      pleroma:
-        %{
-          confirmation_pending: user_info.confirmation_pending,
-          tags: user.tags,
-          is_moderator: user.info.is_moderator,
-          is_admin: user.info.is_admin,
-          relationship: relationship
-        }
-        |> with_notification_settings(user, opts[:for])
+      pleroma: %{
+        confirmation_pending: user_info.confirmation_pending,
+        tags: user.tags,
+        hide_followers: user.info.hide_followers,
+        hide_follows: user.info.hide_follows,
+        hide_favorites: user.info.hide_favorites,
+        relationship: relationship
+      }
     }
+    |> maybe_put_role(user, opts[:for])
+    |> maybe_put_settings(user, opts[:for], user_info)
+    |> maybe_put_notification_settings(user, opts[:for])
   end
 
   defp username_from_nickname(string) when is_binary(string) do
@@ -136,9 +137,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
 
   defp username_from_nickname(_), do: nil
 
-  defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
-    Map.put(data, :notification_settings, user.info.notification_settings)
+  defp maybe_put_settings(
+         data,
+         %User{id: user_id} = user,
+         %User{id: user_id},
+         user_info
+       ) do
+    data
+    |> Kernel.put_in([:source, :privacy], user_info.default_scope)
+    |> Kernel.put_in([:pleroma, :show_role], user.info.show_role)
+    |> Kernel.put_in([:pleroma, :no_rich_text], user.info.no_rich_text)
+  end
+
+  defp maybe_put_settings(data, _, _, _), do: data
+
+  defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
+    data
+    |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
+    |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
+  end
+
+  defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
+    data
+    |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
+    |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator)
+  end
+
+  defp maybe_put_role(data, _, _), do: data
+
+  defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
+    Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)
   end
 
-  defp with_notification_settings(data, _, _), do: data
+  defp maybe_put_notification_settings(data, _, _), do: data
 end
index 0791ed7608e1be3b3318d63a456f067d4d41ea02..39b3f21c0f6ad65ec2e466d863f3d8804666ba72 100644 (file)
@@ -74,52 +74,48 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
       |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
       |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
 
-    data = %{
-      "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
-      "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
-      "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
-      "favourites_count" => 0,
-      "followers_count" => user_info[:follower_count],
-      "following" => following,
-      "follows_you" => follows_you,
-      "statusnet_blocking" => statusnet_blocking,
-      "friends_count" => user_info[:following_count],
-      "id" => user.id,
-      "name" => user.name || user.nickname,
-      "name_html" =>
-        if(user.name,
-          do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
-          else: user.nickname
-        ),
-      "profile_image_url" => image,
-      "profile_image_url_https" => image,
-      "profile_image_url_profile_size" => image,
-      "profile_image_url_original" => image,
-      "rights" => %{
-        "delete_others_notice" => !!user.info.is_moderator,
-        "admin" => !!user.info.is_admin
-      },
-      "screen_name" => user.nickname,
-      "statuses_count" => user_info[:note_count],
-      "statusnet_profile_url" => user.ap_id,
-      "cover_photo" => User.banner_url(user) |> MediaProxy.url(),
-      "background_image" => image_url(user.info.background) |> MediaProxy.url(),
-      "is_local" => user.local,
-      "locked" => user.info.locked,
-      "default_scope" => user.info.default_scope,
-      "no_rich_text" => user.info.no_rich_text,
-      "hide_followers" => user.info.hide_followers,
-      "hide_follows" => user.info.hide_follows,
-      "fields" => fields,
-
-      # Pleroma extension
-      "pleroma" =>
-        %{
-          "confirmation_pending" => user_info.confirmation_pending,
-          "tags" => user.tags
-        }
-        |> maybe_with_activation_status(user, for_user)
-    }
+    data =
+      %{
+        "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
+        "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
+        "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
+        "favourites_count" => 0,
+        "followers_count" => user_info[:follower_count],
+        "following" => following,
+        "follows_you" => follows_you,
+        "statusnet_blocking" => statusnet_blocking,
+        "friends_count" => user_info[:following_count],
+        "id" => user.id,
+        "name" => user.name || user.nickname,
+        "name_html" =>
+          if(user.name,
+            do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
+            else: user.nickname
+          ),
+        "profile_image_url" => image,
+        "profile_image_url_https" => image,
+        "profile_image_url_profile_size" => image,
+        "profile_image_url_original" => image,
+        "screen_name" => user.nickname,
+        "statuses_count" => user_info[:note_count],
+        "statusnet_profile_url" => user.ap_id,
+        "cover_photo" => User.banner_url(user) |> MediaProxy.url(),
+        "background_image" => image_url(user.info.background) |> MediaProxy.url(),
+        "is_local" => user.local,
+        "locked" => user.info.locked,
+        "hide_followers" => user.info.hide_followers,
+        "hide_follows" => user.info.hide_follows,
+        "fields" => fields,
+
+        # Pleroma extension
+        "pleroma" =>
+          %{
+            "confirmation_pending" => user_info.confirmation_pending,
+            "tags" => user.tags
+          }
+          |> maybe_with_activation_status(user, for_user)
+      }
+      |> maybe_with_user_settings(user, for_user)
 
     data =
       if(user.info.is_admin || user.info.is_moderator,
@@ -141,15 +137,35 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
   defp maybe_with_activation_status(data, _, _), do: data
 
   defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
-    Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role})
+    Map.merge(data, %{
+      "role" => role(user),
+      "show_role" => user.info.show_role,
+      "rights" => %{
+        "delete_others_notice" => !!user.info.is_moderator,
+        "admin" => !!user.info.is_admin
+      }
+    })
   end
 
   defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
-    Map.merge(data, %{"role" => role(user)})
+    Map.merge(data, %{
+      "role" => role(user),
+      "rights" => %{
+        "delete_others_notice" => !!user.info.is_moderator,
+        "admin" => !!user.info.is_admin
+      }
+    })
   end
 
   defp maybe_with_role(data, _, _), do: data
 
+  defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do
+    data
+    |> Kernel.put_in(["default_scope"], info.default_scope)
+    |> Kernel.put_in(["no_rich_text"], info.no_rich_text)
+  end
+
+  defp maybe_with_user_settings(data, _, _), do: data
   defp role(%User{info: %{:is_admin => true}}), do: "admin"
   defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
   defp role(_), do: "member"
index 0730201bddd742c48838d1a8f1cee911bb4c9d24..db870f1d138348ea0c0c9c6720cdb4e764723e79 100644 (file)
@@ -56,7 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       bot: false,
       source: %{
         note: "",
-        privacy: "public",
         sensitive: false
       },
       pleroma: %{
@@ -64,6 +63,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
         tags: [],
         is_admin: false,
         is_moderator: false,
+        hide_favorites: true,
+        hide_followers: false,
+        hide_follows: false,
         relationship: %{}
       }
     }
@@ -81,8 +83,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       "follows" => true
     }
 
-    assert %{pleroma: %{notification_settings: ^notification_settings}} =
-             AccountView.render("account.json", %{user: user, for: user})
+    privacy = user.info.default_scope
+
+    assert %{
+             pleroma: %{notification_settings: ^notification_settings},
+             source: %{privacy: ^privacy}
+           } = AccountView.render("account.json", %{user: user, for: user})
   end
 
   test "Represent a Service(bot) account" do
@@ -114,7 +120,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       bot: true,
       source: %{
         note: "",
-        privacy: "public",
         sensitive: false
       },
       pleroma: %{
@@ -122,6 +127,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
         tags: [],
         is_admin: false,
         is_moderator: false,
+        hide_favorites: true,
+        hide_followers: false,
+        hide_follows: false,
         relationship: %{}
       }
     }
@@ -200,7 +208,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
       bot: true,
       source: %{
         note: "",
-        privacy: "public",
         sensitive: false
       },
       pleroma: %{
@@ -208,6 +215,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
         tags: [],
         is_admin: false,
         is_moderator: false,
+        hide_favorites: true,
+        hide_followers: false,
+        hide_follows: false,
         relationship: %{
           id: to_string(user.id),
           following: false,
index a229440887a5bd3aa487536608091ce285e58ca7..0c52dd3e34841d31029536fe234baf23f96b692d 100644 (file)
@@ -2214,6 +2214,66 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert user["locked"] == true
     end
 
+    test "updates the user's hide_followers status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["hide_followers"] == true
+    end
+
+    test "updates the user's hide_follows status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["hide_follows"] == true
+    end
+
+    test "updates the user's hide_favorites status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["hide_favorites"] == true
+    end
+
+    test "updates the user's show_role status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["show_role"] == false
+    end
+
+    test "updates the user's no_rich_text status", %{conn: conn} do
+      user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
+
+      assert user = json_response(conn, 200)
+      assert user["pleroma"]["show_role"] == true
+    end
+
     test "updates the user's name", %{conn: conn} do
       user = insert(:user)
 
index 36b461992b97eb16312753a8bff1326822f12e61..2f9b2af0127a918af8c4831378b4fc557214958d 100644 (file)
@@ -89,17 +89,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "following" => false,
       "follows_you" => false,
       "statusnet_blocking" => false,
-      "rights" => %{
-        "delete_others_notice" => false,
-        "admin" => false
-      },
       "statusnet_profile_url" => user.ap_id,
       "cover_photo" => banner,
       "background_image" => nil,
       "is_local" => true,
       "locked" => false,
-      "default_scope" => "public",
-      "no_rich_text" => false,
       "hide_follows" => false,
       "hide_followers" => false,
       "fields" => [],
@@ -112,6 +106,15 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
     assert represented == UserView.render("show.json", %{user: user})
   end
 
+  test "User exposes settings for themselves and only for themselves", %{user: user} do
+    as_user = UserView.render("show.json", %{user: user, for: user})
+    assert as_user["default_scope"] == user.info.default_scope
+    assert as_user["no_rich_text"] == user.info.no_rich_text
+    as_stranger = UserView.render("show.json", %{user: user})
+    refute as_stranger["default_scope"]
+    refute as_stranger["no_rich_text"]
+  end
+
   test "A user for a given other follower", %{user: user} do
     follower = insert(:user, %{following: [User.ap_followers(user)]})
     {:ok, user} = User.update_follower_count(user)
@@ -137,17 +140,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "following" => true,
       "follows_you" => false,
       "statusnet_blocking" => false,
-      "rights" => %{
-        "delete_others_notice" => false,
-        "admin" => false
-      },
       "statusnet_profile_url" => user.ap_id,
       "cover_photo" => banner,
       "background_image" => nil,
       "is_local" => true,
       "locked" => false,
-      "default_scope" => "public",
-      "no_rich_text" => false,
       "hide_follows" => false,
       "hide_followers" => false,
       "fields" => [],
@@ -186,17 +183,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "following" => false,
       "follows_you" => true,
       "statusnet_blocking" => false,
-      "rights" => %{
-        "delete_others_notice" => false,
-        "admin" => false
-      },
       "statusnet_profile_url" => follower.ap_id,
       "cover_photo" => banner,
       "background_image" => nil,
       "is_local" => true,
       "locked" => false,
-      "default_scope" => "public",
-      "no_rich_text" => false,
       "hide_follows" => false,
       "hide_followers" => false,
       "fields" => [],
@@ -272,17 +263,11 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
       "following" => false,
       "follows_you" => false,
       "statusnet_blocking" => true,
-      "rights" => %{
-        "delete_others_notice" => false,
-        "admin" => false
-      },
       "statusnet_profile_url" => user.ap_id,
       "cover_photo" => banner,
       "background_image" => nil,
       "is_local" => true,
       "locked" => false,
-      "default_scope" => "public",
-      "no_rich_text" => false,
       "hide_follows" => false,
       "hide_followers" => false,
       "fields" => [],