Merge branch 'fix/credo-issues-test' into 'develop'
authorkaniini <nenolod@gmail.com>
Thu, 14 Feb 2019 03:55:26 +0000 (03:55 +0000)
committerkaniini <nenolod@gmail.com>
Thu, 14 Feb 2019 03:55:26 +0000 (03:55 +0000)
Fix credo issues in ./test

See merge request pleroma/pleroma!808

28 files changed:
config/dev.exs
docs/Clients.md
lib/pleroma/notification.ex
lib/pleroma/object.ex
lib/pleroma/plugs/http_security_plug.ex
lib/pleroma/thread_mute.ex [new file with mode: 0644]
lib/pleroma/uploaders/mdii.ex
lib/pleroma/user.ex
lib/pleroma/web/activity_pub/mrf/keyword_policy.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/activity_pub/views/user_view.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/rich_media/parser.ex
lib/pleroma/web/router.ex
lib/pleroma/web/templates/layout/app.html.eex
lib/pleroma/web/templates/o_auth/o_auth/show.html.eex
lib/pleroma/web/twitter_api/twitter_api.ex
priv/repo/migrations/20190205114625_create_thread_mutes.exs [new file with mode: 0644]
priv/static/schemas/litepub-0.1.jsonld
test/object_test.exs
test/user_test.exs
test/web/activity_pub/activity_pub_controller_test.exs
test/web/activity_pub/mrf/keyword_policy_test.exs
test/web/activity_pub/views/user_view_test.exs
test/web/common_api/common_api_test.exs
test/web/mastodon_api/mastodon_api_controller_test.exs

index 8f89aa03c3d4a68e0097a652fc9e24f6d1c5c2af..f77bb99765f4014bb51db91b3fc8fb318ed8f3bd 100644 (file)
@@ -16,7 +16,8 @@ config :pleroma, Pleroma.Web.Endpoint,
   debug_errors: true,
   code_reloader: true,
   check_origin: false,
-  watchers: []
+  watchers: [],
+  secure_cookie_flag: false
 
 config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local
 
index 057f12392b7dacf46497eb6345d8caf1c0165b4b..c3d77648845e3dfaf3d950fd552cda97cf99916f 100644 (file)
@@ -7,6 +7,7 @@ Feel free to contact us to be added to this list!
 - Homepage: <http://www.pleroma.com/desktop-app/>
 - Source Code: ???
 - Platforms: Windows, Mac, (Linux?)
+- Features: Streaming Ready
 
 ### Social
 - Source Code: <https://gitlab.gnome.org/BrainBlasted/Social>
@@ -19,6 +20,7 @@ Feel free to contact us to be added to this list!
 - Source Code: <https://github.com/h3poteto/whalebird-desktop>
 - Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto)
 - Platforms: Windows, Mac, Linux
+- Features: Streaming Ready
 
 ## Handheld
 ### Amaroq
@@ -26,60 +28,71 @@ Feel free to contact us to be added to this list!
 - Source Code: <https://github.com/ReticentJohn/Amaroq>
 - Contact: [@eurasierboy@mastodon.social](https://mastodon.social/users/eurasierboy)
 - Platforms: iOS
+- Features: No Streaming
 
 ### Nekonium
 - Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)
 - Source: <https://git.gdgd.jp.net/lin/nekonium/>
 - Contact: [@lin@pleroma.gdgd.jp.net](https://pleroma.gdgd.jp.net/users/lin)
 - Platforms: Android
+- Features: Streaming Ready
 
 ### Mastalab
 - Source Code: <https://gitlab.com/tom79/mastalab/>
 - Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79)
 - Platforms: Android
+- Features: Streaming Ready
 
 ### Roma
 - Homepage: <http://www.pleroma.com/>
 - Source Code: ???
 - Platforms: iOS, Android
+- Features: No Streaming
 
 ### Tootdon
 - Homepage: <http://tootdon.club/>, <http://blog.mastodon-tootdon.com/>
 - Source Code: ???
 - Contact: [@tootdon@mstdn.jp](https://mstdn.jp/users/tootdon)
 - Platforms: Android, iOS
+- Features: No Streaming
 
 ### Tusky
 - Homepage: <https://tuskyapp.github.io/>
 - Source Code: <https://github.com/tuskyapp/Tusky>
 - Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck)
 - Platforms: Android
+- Features: No Streaming
 
 ### Twidere
 - Homepage: <https://twidere.mariotaku.org/>
 - Source Code: <https://github.com/TwidereProject/Twidere-Android/>, <https://github.com/TwidereProject/Twidere-iOS/>
 - Contact: <me@mariotaku.org>
 - Platform: Android, iOS
+- Features: No Streaming
 
 ## Alternative Web Interfaces
 ### Brutaldon
 - Homepage: <https://jfm.carcosa.net/projects/software/brutaldon/>
 - Source Code: <https://github.com/jfmcbrayer/brutaldon>
 - Contact: [@gcupc@glitch.social](https://glitch.social/users/gcupc)
+- Features: No Streaming
 
 ### Feather
 - Source Code: <https://github.com/kaniini/feather>
 - Contact: [@kaniini@pleroma.site](https://pleroma.site/kaniini)
+- Features: No Streaming
 
 ### Halcyon
 - Source Code: <https://notabug.org/halcyon-suite/halcyon>
 - Contact: [@halcyon@social.csswg.org](https://social.csswg.org/users/halcyon)
+- Features: Streaming Ready
 
 ### Pinafore
 - Homepage: <https://pinafore.social/>
 - Source Code: <https://github.com/nolanlawson/pinafore>
 - Contact: [@pinafore@mastodon.technology](https://mastodon.technology/users/pinafore)
 - Note: Pleroma support is a secondary goal
+- Features: No Streaming
 
 ### Sengi
 - Source Code: <https://github.com/NicolasConstant/sengi>
index c7c925c890a16c4c93fbfdd55a02b3b57259950c..c88512567bada8f768b6000bc18575afe3110d09 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Notification do
   alias Pleroma.Notification
   alias Pleroma.Repo
   alias Pleroma.Web.CommonAPI.Utils
+  alias Pleroma.Web.CommonAPI
 
   import Ecto.Query
 
@@ -117,7 +118,7 @@ defmodule Pleroma.Notification do
   # TODO move to sql, too.
   def create_notification(%Activity{} = activity, %User{} = user) do
     unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
-             user.ap_id == activity.data["actor"] or
+             CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or
              (activity.data["type"] == "Follow" and
                 Enum.any?(Notification.for_user(user), fn notif ->
                   notif.activity.data["type"] == "Follow" and
index dabb495364277fdd0299a5e185db8947f26fd8bc..5f1fc801b592d70da0534fdb9defd34e99880758 100644 (file)
@@ -20,29 +20,9 @@ defmodule Pleroma.Object do
     timestamps()
   end
 
-  def insert_or_get(cng) do
-    {_, data} = fetch_field(cng, :data)
-    id = data["id"] || data[:id]
-    key = "object:#{id}"
-
-    fetcher = fn _ ->
-      with nil <- get_by_ap_id(id),
-           {:ok, object} <- Repo.insert(cng) do
-        {:commit, object}
-      else
-        %Object{} = object -> {:commit, object}
-        e -> {:ignore, e}
-      end
-    end
-
-    with {state, object} when state in [:commit, :ok] <- Cachex.fetch(:object_cache, key, fetcher) do
-      {:ok, object}
-    end
-  end
-
   def create(data) do
     Object.change(%Object{}, %{data: data})
-    |> insert_or_get()
+    |> Repo.insert()
   end
 
   def change(struct, params \\ %{}) do
index 2a266c407fb8131b03e0e323d67446f61a112551..057553e2414757eaed4fd40f96774dc7af2babb9 100644 (file)
@@ -33,7 +33,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
   end
 
   defp csp_string do
-    protocol = Config.get([Pleroma.Web.Endpoint, :protocol])
+    scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
+    websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
+
+    connect_src =
+      if Mix.env() == :dev do
+        "connect-src 'self' http://localhost:3035/ " <> websocket_url
+      else
+        "connect-src 'self' " <> websocket_url
+      end
+
+    script_src =
+      if Mix.env() == :dev do
+        "script-src 'self' 'unsafe-eval'"
+      else
+        "script-src 'self'"
+      end
 
     [
       "default-src 'none'",
@@ -43,10 +58,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       "media-src 'self' https:",
       "style-src 'self' 'unsafe-inline'",
       "font-src 'self'",
-      "script-src 'self'",
-      "connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"),
       "manifest-src 'self'",
-      if protocol == "https" do
+      connect_src,
+      script_src,
+      if scheme == "https" do
         "upgrade-insecure-requests"
       end
     ]
diff --git a/lib/pleroma/thread_mute.ex b/lib/pleroma/thread_mute.ex
new file mode 100644 (file)
index 0000000..0b57711
--- /dev/null
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ThreadMute do
+  use Ecto.Schema
+  alias Pleroma.{Repo, User, ThreadMute}
+  require Ecto.Query
+
+  schema "thread_mutes" do
+    belongs_to(:user, User, type: Pleroma.FlakeId)
+    field(:context, :string)
+  end
+
+  def changeset(mute, params \\ %{}) do
+    mute
+    |> Ecto.Changeset.cast(params, [:user_id, :context])
+    |> Ecto.Changeset.foreign_key_constraint(:user_id)
+    |> Ecto.Changeset.unique_constraint(:user_id, name: :unique_index)
+  end
+
+  def query(user_id, context) do
+    user_id = Pleroma.FlakeId.from_string(user_id)
+
+    ThreadMute
+    |> Ecto.Query.where(user_id: ^user_id)
+    |> Ecto.Query.where(context: ^context)
+  end
+
+  def add_mute(user_id, context) do
+    %ThreadMute{}
+    |> changeset(%{user_id: user_id, context: context})
+    |> Repo.insert()
+  end
+
+  def remove_mute(user_id, context) do
+    query(user_id, context)
+    |> Repo.delete_all()
+  end
+
+  def check_muted(user_id, context) do
+    query(user_id, context)
+    |> Repo.all()
+  end
+end
index 320b07abde423dc597cd0585161002223854c514..190ed9f3ac77e93fa5494597deb6bc113e96b887 100644 (file)
@@ -25,7 +25,7 @@ defmodule Pleroma.Uploaders.MDII do
     query = "#{cgi}?#{extension}"
 
     with {:ok, %{status: 200, body: body}} <-
-           @httpoison.post(query, file_data, adapter: [pool: :default]) do
+           @httpoison.post(query, file_data, [], adapter: [pool: :default]) do
       remote_file_name = String.split(body) |> List.first()
       public_url = "#{files}/#{remote_file_name}.#{extension}"
       {:ok, {:url, public_url}}
index 0060d966bd17289cdfe7fba93dd88acbc7f192a9..3232cb8421e50f7596c778b1482f0b92c70f2f36 100644 (file)
@@ -311,12 +311,12 @@ defmodule Pleroma.User do
     end
   end
 
-  @doc "A mass follow for local users. Respects blocks but does not create activities."
+  @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
   @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
   def follow_all(follower, followeds) do
     followed_addresses =
       followeds
-      |> Enum.reject(fn %{ap_id: ap_id} -> ap_id in follower.info.blocks end)
+      |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
       |> Enum.map(fn %{follower_address: fa} -> fa end)
 
     q =
index ce6d2e529c4ab8072afbb8318ebc831a7ecd0d40..5fdc0341452107e9826c0e1aa5a480471daa5d46 100644 (file)
@@ -12,9 +12,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
     String.match?(string, pattern)
   end
 
-  defp check_reject(%{"object" => %{"content" => content}} = message) do
+  defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do
     if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern ->
-         string_matches?(content, pattern)
+         string_matches?(content, pattern) or string_matches?(summary, pattern)
        end) do
       {:reject, nil}
     else
@@ -22,10 +22,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
     end
   end
 
-  defp check_ftl_removal(%{"to" => to, "object" => %{"content" => content}} = message) do
+  defp check_ftl_removal(
+         %{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
+       ) do
     if "https://www.w3.org/ns/activitystreams#Public" in to and
          Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
-           string_matches?(content, pattern)
+           string_matches?(content, pattern) or string_matches?(summary, pattern)
          end) do
       to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
       cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
@@ -41,14 +43,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
     end
   end
 
-  defp check_replace(%{"object" => %{"content" => content}} = message) do
-    content =
-      Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), content, fn {pattern, replacement},
-                                                                            acc ->
-        String.replace(acc, pattern, replacement)
+  defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do
+    {content, summary} =
+      Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern,
+                                                                                        replacement},
+                                                                                       {content_acc,
+                                                                                        summary_acc} ->
+        {String.replace(content_acc, pattern, replacement),
+         String.replace(summary_acc, pattern, replacement)}
       end)
 
-    {:ok, put_in(message["object"]["content"], content)}
+    {:ok,
+     message
+     |> put_in(["object", "content"], content)
+     |> put_in(["object", "summary"], summary)}
   end
 
   @impl true
index da6cca4ddd4052b93013b8c0c3b30a4fd88002b0..964e11c9d424caae12a43dff5e6c2d46157ad06c 100644 (file)
@@ -142,8 +142,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do
     context = context || generate_id("contexts")
     changeset = Object.context_mapping(context)
 
-    with {:ok, object} <- Object.insert_or_get(changeset) do
-      object
+    case Repo.insert(changeset) do
+      {:ok, object} ->
+        object
+
+      # This should be solved by an upsert, but it seems ecto
+      # has problems accessing the constraint inside the jsonb.
+      {:error, _} ->
+        Object.get_cached_by_ap_id(context)
     end
   end
 
index 15e6c1f687c5ee755b7a2bc759e9cf90ff4235ff..c8e15498986d71a91a1c27cd6db6691d4bcc2b3d 100644 (file)
@@ -12,9 +12,26 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.Utils
+  alias Pleroma.Web.Router.Helpers
+  alias Pleroma.Web.Endpoint
 
   import Ecto.Query
 
+  def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
+    %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
+  end
+
+  def render("endpoints.json", %{user: %User{local: true} = _user}) do
+    %{
+      "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
+      "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
+      "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
+      "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)
+    }
+  end
+
+  def render("endpoints.json", _), do: %{}
+
   # the instance itself is not a Person, but instead an Application
   def render("user.json", %{user: %{nickname: nil} = user}) do
     {:ok, user} = WebFinger.ensure_keys_present(user)
@@ -22,6 +39,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
     public_key = :public_key.pem_encode([public_key])
 
+    endpoints = render("endpoints.json", %{user: user})
+
     %{
       "id" => user.ap_id,
       "type" => "Application",
@@ -37,9 +56,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
         "owner" => user.ap_id,
         "publicKeyPem" => public_key
       },
-      "endpoints" => %{
-        "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
-      }
+      "endpoints" => endpoints
     }
     |> Map.merge(Utils.make_json_ld_header())
   end
@@ -50,6 +67,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
     public_key = :public_key.pem_encode([public_key])
 
+    endpoints = render("endpoints.json", %{user: user})
+
     %{
       "id" => user.ap_id,
       "type" => "Person",
@@ -67,9 +86,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
         "owner" => user.ap_id,
         "publicKeyPem" => public_key
       },
-      "endpoints" => %{
-        "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox"
-      },
+      "endpoints" => endpoints,
       "icon" => %{
         "type" => "Image",
         "url" => User.avatar_url(user)
@@ -88,7 +105,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     query = from(user in query, select: [:ap_id])
     following = Repo.all(query)
 
-    collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows)
+    total =
+      if !user.info.hide_follows do
+        length(following)
+      else
+        0
+      end
+
+    collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total)
     |> Map.merge(Utils.make_json_ld_header())
   end
 
@@ -97,10 +121,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     query = from(user in query, select: [:ap_id])
     following = Repo.all(query)
 
+    total =
+      if !user.info.hide_follows do
+        length(following)
+      else
+        0
+      end
+
     %{
       "id" => "#{user.ap_id}/following",
       "type" => "OrderedCollection",
-      "totalItems" => length(following),
+      "totalItems" => total,
       "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows)
     }
     |> Map.merge(Utils.make_json_ld_header())
@@ -111,7 +142,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     query = from(user in query, select: [:ap_id])
     followers = Repo.all(query)
 
-    collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers)
+    total =
+      if !user.info.hide_followers do
+        length(followers)
+      else
+        0
+      end
+
+    collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total)
     |> Map.merge(Utils.make_json_ld_header())
   end
 
@@ -120,19 +158,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     query = from(user in query, select: [:ap_id])
     followers = Repo.all(query)
 
+    total =
+      if !user.info.hide_followers do
+        length(followers)
+      else
+        0
+      end
+
     %{
       "id" => "#{user.ap_id}/followers",
       "type" => "OrderedCollection",
-      "totalItems" => length(followers),
-      "first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers)
+      "totalItems" => total,
+      "first" =>
+        collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total)
     }
     |> Map.merge(Utils.make_json_ld_header())
   end
 
   def render("outbox.json", %{user: user, max_id: max_qid}) do
-    # XXX: technically note_count is wrong for this, but it's better than nothing
-    info = User.user_info(user)
-
     params = %{
       "limit" => "10"
     }
@@ -160,7 +203,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "id" => "#{iri}?max_id=#{max_id}",
       "type" => "OrderedCollectionPage",
       "partOf" => iri,
-      "totalItems" => info.note_count,
       "orderedItems" => collection,
       "next" => "#{iri}?max_id=#{min_id}"
     }
@@ -169,7 +211,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       %{
         "id" => iri,
         "type" => "OrderedCollection",
-        "totalItems" => info.note_count,
         "first" => page
       }
       |> Map.merge(Utils.make_json_ld_header())
@@ -207,7 +248,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       "id" => "#{iri}?max_id=#{max_id}",
       "type" => "OrderedCollectionPage",
       "partOf" => iri,
-      "totalItems" => -1,
       "orderedItems" => collection,
       "next" => "#{iri}?max_id=#{min_id}"
     }
@@ -216,7 +256,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
       %{
         "id" => iri,
         "type" => "OrderedCollection",
-        "totalItems" => -1,
         "first" => page
       }
       |> Map.merge(Utils.make_json_ld_header())
index c0d6fb5c4743272b6be60f892c599030fa07a3c4..86f249c54fb0fe620f2d48d8f25b414b7ba02477 100644 (file)
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
   alias Pleroma.Repo
   alias Pleroma.Activity
   alias Pleroma.Object
+  alias Pleroma.ThreadMute
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.Utils
   alias Pleroma.Formatter
@@ -219,4 +220,27 @@ defmodule Pleroma.Web.CommonAPI do
         {:error, "Could not unpin"}
     end
   end
+
+  def add_mute(user, activity) do
+    with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do
+      {:ok, activity}
+    else
+      {:error, _} -> {:error, "conversation is already muted"}
+    end
+  end
+
+  def remove_mute(user, activity) do
+    ThreadMute.remove_mute(user.id, activity.data["context"])
+    {:ok, activity}
+  end
+
+  def thread_muted?(%{id: nil} = _user, _activity), do: false
+
+  def thread_muted?(user, activity) do
+    with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do
+      false
+    else
+      _ -> true
+    end
+  end
 end
index 06f8703935674c887cb323d3a0ed0cc8a9ec064a..dcaeccac698d0bee14dee817dcc48a11b8dc06fe 100644 (file)
@@ -456,6 +456,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
     end
   end
 
+  def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    activity = Activity.get_by_id(id)
+
+    with {:ok, activity} <- CommonAPI.add_mute(user, activity) do
+      conn
+      |> put_view(StatusView)
+      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+    else
+      {:error, reason} ->
+        conn
+        |> put_resp_content_type("application/json")
+        |> send_resp(:bad_request, Jason.encode!(%{"error" => reason}))
+    end
+  end
+
+  def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+    activity = Activity.get_by_id(id)
+
+    with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
+      conn
+      |> put_view(StatusView)
+      |> try_render("status.json", %{activity: activity, for: user, as: :activity})
+    end
+  end
+
   def notifications(%{assigns: %{user: user}} = conn, params) do
     notifications = Notification.for_user(user, params)
 
index f51a2ebb0e5ccd4d6039ce962886f1dfcf39da3b..69f5f992c9bf7b365f57436fe942971794cc8a40 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   alias Pleroma.HTML
   alias Pleroma.Repo
   alias Pleroma.User
+  alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MastodonAPI.StatusView
@@ -160,7 +161,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       reblogged: present?(repeated),
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
-      muted: false,
+      muted: CommonAPI.thread_muted?(user, activity),
       pinned: pinned?(activity, user),
       sensitive: sensitive,
       spoiler_text: object["summary"] || "",
index 38f1cdeecb5c83ded6d18ca0357cd92700d8d896..4341141df8504d67ba2d5b5129dc8ff611e24e42 100644 (file)
@@ -9,6 +9,13 @@ defmodule Pleroma.Web.RichMedia.Parser do
     Pleroma.Web.RichMedia.Parsers.OEmbed
   ]
 
+  @hackney_options [
+    pool: :media,
+    timeout: 2_000,
+    recv_timeout: 2_000,
+    max_body: 2_000_000
+  ]
+
   def parse(nil), do: {:error, "No URL provided"}
 
   if Mix.env() == :test do
@@ -28,7 +35,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
 
   defp parse_url(url) do
     try do
-      {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
+      {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options)
 
       html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data()
     rescue
index 7f606ac404683c389a7328f991cbaf1d1e69ff87..d66a1c2a136b2d60eec02d900e747a5d90b99790 100644 (file)
@@ -198,6 +198,8 @@ defmodule Pleroma.Web.Router do
     post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
     post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
     post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
+    post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
+    post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
 
     post("/notifications/clear", MastodonAPIController, :clear_notifications)
     post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
@@ -466,8 +468,8 @@ defmodule Pleroma.Web.Router do
 
   scope "/", Pleroma.Web.ActivityPub do
     pipe_through(:activitypub)
-    post("/users/:nickname/inbox", ActivityPubController, :inbox)
     post("/inbox", ActivityPubController, :inbox)
+    post("/users/:nickname/inbox", ActivityPubController, :inbox)
   end
 
   scope "/.well-known", Pleroma.Web do
index 8dd3284d63e147151c7e16cd374062ca56ceda4f..520e4b3d5ceb09c49f06b21e17fd5c9e7dbc492d 100644 (file)
         font-weight: 500;
         font-size: 16px;
       }
+
+      .alert-danger {
+        box-sizing: border-box;
+        width: 100%;
+        color: #D8000C;
+        background-color: #FFD2D2;
+        border-radius: 4px;
+        border: none;
+        padding: 10px;
+        margin-top: 20px;
+        font-weight: 500;
+        font-size: 16px;
+      }
+
+      .alert-info {
+        box-sizing: border-box;
+        width: 100%;
+        color: #00529B;
+        background-color: #BDE5F8;
+        border-radius: 4px;
+        border: none;
+        padding: 10px;
+        margin-top: 20px;
+        font-weight: 500;
+        font-size: 16px;
+      }
     </style>
   </head>
   <body>
index de2241ec91dd283d38baf7d0d5db3b49d8c1d2ce..32c458f0c4676e8d4986d40c69797c8d406b344b 100644 (file)
@@ -1,5 +1,9 @@
+<%= if get_flash(@conn, :info) do %>
 <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
+<% end %>
+<%= if get_flash(@conn, :error) do %>
 <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
+<% end %>
 <h2>OAuth Authorization</h2>
 <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %>
 <%= label f, :name, "Name or email" %>
index 162beb9be06b7ffb7f95b51efb434ed3d5d1871a..db521a3ad09599568d52dc597c2b1e469e8dc6e4 100644 (file)
@@ -310,8 +310,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
     else
       _e ->
         changeset = Object.context_mapping(context)
-        {:ok, object} = Object.insert_or_get(changeset)
-        object.id
+
+        case Repo.insert(changeset) do
+          {:ok, %{id: id}} ->
+            id
+
+          # This should be solved by an upsert, but it seems ecto
+          # has problems accessing the constraint inside the jsonb.
+          {:error, _} ->
+            Object.get_cached_by_ap_id(context).id
+        end
     end
   end
 
diff --git a/priv/repo/migrations/20190205114625_create_thread_mutes.exs b/priv/repo/migrations/20190205114625_create_thread_mutes.exs
new file mode 100644 (file)
index 0000000..8e9eccb
--- /dev/null
@@ -0,0 +1,12 @@
+defmodule Pleroma.Repo.Migrations.CreateThreadMutes do
+  use Ecto.Migration
+
+  def change do
+    create table(:thread_mutes) do
+      add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
+      add :context, :string
+    end
+    
+    create unique_index(:thread_mutes, [:user_id, :context], name: :unique_index)
+  end
+end
index 15645646a3dbe04cad7af2099e04e4562db17e39..f36b231c5b070c8654aa766f40f98ff40b416736 100644 (file)
             "value": "schema:value",
             "sensitive": "as:sensitive",
             "litepub": "http://litepub.social/ns#",
-            "directMessage": "litepub:directMessage"
+            "directMessage": "litepub:directMessage",
+            "oauthRegistrationEndpoint": {
+                "@id": "litepub:oauthRegistrationEndpoint",
+                "@type": "@id"
+            }
         }
     ]
 }
index 9f5283d2d73ad7cad7a27a1ef264414a75747184..a820a34ee87db85676218dd0b8589f5470ebcc89 100644 (file)
@@ -58,32 +58,4 @@ defmodule Pleroma.ObjectTest do
       assert cached_object.data["type"] == "Tombstone"
     end
   end
-
-  describe "insert_or_get" do
-    test "inserting the same object twice (by id) just returns the original object" do
-      data = %{data: %{"id" => Ecto.UUID.generate()}}
-      cng = Object.change(%Object{}, data)
-      {:ok, object} = Object.insert_or_get(cng)
-      {:ok, second_object} = Object.insert_or_get(cng)
-
-      Cachex.clear(:object_cache)
-      {:ok, third_object} = Object.insert_or_get(cng)
-
-      assert object == second_object
-      assert object == third_object
-    end
-  end
-
-  describe "create" do
-    test "inserts an object for a given data set" do
-      data = %{"id" => Ecto.UUID.generate()}
-
-      {:ok, object} = Object.create(data)
-      assert object.data["id"] == data["id"]
-
-      # Works when doing it twice.
-      {:ok, object} = Object.create(data)
-      assert object.data["id"] == data["id"]
-    end
-  end
 end
index 1fd54b944d8c06d6ffbd2bd525e1303d61aab18b..58587bd822896be326ebc338eb6ce02820a2aa06 100644 (file)
@@ -57,18 +57,21 @@ defmodule Pleroma.UserTest do
     followed_two = insert(:user)
     blocked = insert(:user)
     not_followed = insert(:user)
+    reverse_blocked = insert(:user)
 
     {:ok, user} = User.block(user, blocked)
+    {:ok, reverse_blocked} = User.block(reverse_blocked, user)
 
     {:ok, user} = User.follow(user, followed_zero)
 
-    {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked])
+    {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked])
 
     assert User.following?(user, followed_one)
     assert User.following?(user, followed_two)
     assert User.following?(user, followed_zero)
     refute User.following?(user, not_followed)
     refute User.following?(user, blocked)
+    refute User.following?(user, reverse_blocked)
   end
 
   test "follow_all follows mutliple users without duplicating" do
index acd295988788004f8e24d7f6ddcf4aa50a0df3aa..398bedf7793f378b6a4e48a6e2adec5917497582 100644 (file)
@@ -402,7 +402,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
         |> json_response(200)
 
       assert result["first"]["orderedItems"] == []
-      assert result["totalItems"] == 1
+      assert result["totalItems"] == 0
     end
 
     test "it works for more than 10 users", %{conn: conn} do
@@ -457,7 +457,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
         |> json_response(200)
 
       assert result["first"]["orderedItems"] == []
-      assert result["totalItems"] == 1
+      assert result["totalItems"] == 0
     end
 
     test "it works for more than 10 users", %{conn: conn} do
index 67a5858d71b00b2dad4a465303257ca52638949f..602892a3702ab14d43ec99a35080e783686d3481 100644 (file)
@@ -12,18 +12,35 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
   end
 
   describe "rejecting based on keywords" do
-    test "rejects if string matches" do
+    test "rejects if string matches in content" do
       Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
 
       message = %{
         "type" => "Create",
-        "object" => %{"content" => "just a daily reminder that compLAINer is a good pun"}
+        "object" => %{
+          "content" => "just a daily reminder that compLAINer is a good pun",
+          "summary" => ""
+        }
       }
 
       assert {:reject, nil} == KeywordPolicy.filter(message)
     end
 
-    test "rejects if regex matches" do
+    test "rejects if string matches in summary" do
+      Pleroma.Config.put([:mrf_keyword, :reject], ["pun"])
+
+      message = %{
+        "type" => "Create",
+        "object" => %{
+          "summary" => "just a daily reminder that compLAINer is a good pun",
+          "content" => ""
+        }
+      }
+
+      assert {:reject, nil} == KeywordPolicy.filter(message)
+    end
+
+    test "rejects if regex matches in content" do
       Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
 
       assert true ==
@@ -31,7 +48,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
                  message = %{
                    "type" => "Create",
                    "object" => %{
-                     "content" => "just a daily reminder that #{content} is a good pun"
+                     "content" => "just a daily reminder that #{content} is a good pun",
+                     "summary" => ""
+                   }
+                 }
+
+                 {:reject, nil} == KeywordPolicy.filter(message)
+               end)
+    end
+
+    test "rejects if regex matches in summary" do
+      Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/])
+
+      assert true ==
+               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+                 message = %{
+                   "type" => "Create",
+                   "object" => %{
+                     "summary" => "just a daily reminder that #{content} is a good pun",
+                     "content" => ""
                    }
                  }
 
@@ -41,13 +76,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
   end
 
   describe "delisting from ftl based on keywords" do
-    test "delists if string matches" do
+    test "delists if string matches in content" do
       Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
 
       message = %{
         "to" => ["https://www.w3.org/ns/activitystreams#Public"],
         "type" => "Create",
-        "object" => %{"content" => "just a daily reminder that compLAINer is a good pun"}
+        "object" => %{
+          "content" => "just a daily reminder that compLAINer is a good pun",
+          "summary" => ""
+        }
       }
 
       {:ok, result} = KeywordPolicy.filter(message)
@@ -55,7 +93,45 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
       refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
     end
 
-    test "delists if regex matches" do
+    test "delists if string matches in summary" do
+      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"])
+
+      message = %{
+        "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "summary" => "just a daily reminder that compLAINer is a good pun",
+          "content" => ""
+        }
+      }
+
+      {:ok, result} = KeywordPolicy.filter(message)
+      assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"]
+      refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"]
+    end
+
+    test "delists if regex matches in content" do
+      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
+
+      assert true ==
+               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content ->
+                 message = %{
+                   "type" => "Create",
+                   "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+                   "object" => %{
+                     "content" => "just a daily reminder that #{content} is a good pun",
+                     "summary" => ""
+                   }
+                 }
+
+                 {:ok, result} = KeywordPolicy.filter(message)
+
+                 ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and
+                   not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"])
+               end)
+    end
+
+    test "delists if regex matches in summary" do
       Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/])
 
       assert true ==
@@ -64,7 +140,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
                    "type" => "Create",
                    "to" => ["https://www.w3.org/ns/activitystreams#Public"],
                    "object" => %{
-                     "content" => "just a daily reminder that #{content} is a good pun"
+                     "summary" => "just a daily reminder that #{content} is a good pun",
+                     "content" => ""
                    }
                  }
 
@@ -77,20 +154,33 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
   end
 
   describe "replacing keywords" do
-    test "replaces keyword if string matches" do
+    test "replaces keyword if string matches in content" do
       Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
 
       message = %{
         "type" => "Create",
         "to" => ["https://www.w3.org/ns/activitystreams#Public"],
-        "object" => %{"content" => "ZFS is opensource"}
+        "object" => %{"content" => "ZFS is opensource", "summary" => ""}
       }
 
       {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
       assert result == "ZFS is free software"
     end
 
-    test "replaces keyword if regex matches" do
+    test "replaces keyword if string matches in summary" do
+      Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}])
+
+      message = %{
+        "type" => "Create",
+        "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "object" => %{"summary" => "ZFS is opensource", "content" => ""}
+      }
+
+      {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+      assert result == "ZFS is free software"
+    end
+
+    test "replaces keyword if regex matches in content" do
       Pleroma.Config.put([:mrf_keyword, :replace], [
         {~r/open(-|\s)?source\s?(software)?/, "free software"}
       ])
@@ -100,12 +190,30 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do
                  message = %{
                    "type" => "Create",
                    "to" => ["https://www.w3.org/ns/activitystreams#Public"],
-                   "object" => %{"content" => "ZFS is #{content}"}
+                   "object" => %{"content" => "ZFS is #{content}", "summary" => ""}
                  }
 
                  {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message)
                  result == "ZFS is free software"
                end)
     end
+
+    test "replaces keyword if regex matches in summary" do
+      Pleroma.Config.put([:mrf_keyword, :replace], [
+        {~r/open(-|\s)?source\s?(software)?/, "free software"}
+      ])
+
+      assert true ==
+               Enum.all?(["opensource", "open-source", "open source"], fn content ->
+                 message = %{
+                   "type" => "Create",
+                   "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+                   "object" => %{"summary" => "ZFS is #{content}", "content" => ""}
+                 }
+
+                 {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message)
+                 result == "ZFS is free software"
+               end)
+    end
   end
 end
index 7fc870e962753840ef6dca0fa70bd627491c00c6..0bc1d4728f2296f29b5c52cae442beef1b139fa0 100644 (file)
@@ -15,4 +15,43 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
 
     assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")
   end
+
+  describe "endpoints" do
+    test "local users have a usable endpoints structure" do
+      user = insert(:user)
+      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+      result = UserView.render("user.json", %{user: user})
+
+      assert result["id"] == user.ap_id
+
+      %{
+        "sharedInbox" => _,
+        "oauthAuthorizationEndpoint" => _,
+        "oauthRegistrationEndpoint" => _,
+        "oauthTokenEndpoint" => _
+      } = result["endpoints"]
+    end
+
+    test "remote users have an empty endpoints structure" do
+      user = insert(:user, local: false)
+      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+      result = UserView.render("user.json", %{user: user})
+
+      assert result["id"] == user.ap_id
+      assert result["endpoints"] == %{}
+    end
+
+    test "instance users do not expose oAuth endpoints" do
+      user = insert(:user, nickname: nil, local: true)
+      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
+
+      result = UserView.render("user.json", %{user: user})
+
+      refute result["endpoints"]["oauthAuthorizationEndpoint"]
+      refute result["endpoints"]["oauthRegistrationEndpoint"]
+      refute result["endpoints"]["oauthTokenEndpoint"]
+    end
+  end
 end
index a7d9e61613b6f94785e3cf3298dcc5e4de4cc1e2..d26b6e49c1dcfda64090ee80bc81e0d9216e723d 100644 (file)
@@ -164,4 +164,30 @@ defmodule Pleroma.Web.CommonAPI.Test do
       assert %User{info: %{pinned_activities: []}} = user
     end
   end
+
+  describe "mute tests" do
+    setup do
+      user = insert(:user)
+
+      activity = insert(:note_activity)
+
+      [user: user, activity: activity]
+    end
+
+    test "add mute", %{user: user, activity: activity} do
+      {:ok, _} = CommonAPI.add_mute(user, activity)
+      assert CommonAPI.thread_muted?(user, activity)
+    end
+
+    test "remove mute", %{user: user, activity: activity} do
+      CommonAPI.add_mute(user, activity)
+      {:ok, _} = CommonAPI.remove_mute(user, activity)
+      refute CommonAPI.thread_muted?(user, activity)
+    end
+
+    test "check that mutes can't be duplicate", %{user: user, activity: activity} do
+      CommonAPI.add_mute(user, activity)
+      {:error, _} = CommonAPI.add_mute(user, activity)
+    end
+  end
 end
index 19393f3555d65fd16a89cd28d501d0efcad12879..26c9c25a619b7e2fe2d7cb16d06f9d3e82c499e5 100644 (file)
@@ -1754,4 +1754,36 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
 
     assert [json_response(response2, 200)] == json_response(bookmarks, 200)
   end
+
+  describe "conversation muting" do
+    setup do
+      user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
+
+      [user: user, activity: activity]
+    end
+
+    test "mute conversation", %{conn: conn, user: user, activity: activity} do
+      id_str = to_string(activity.id)
+
+      assert %{"id" => ^id_str, "muted" => true} =
+               conn
+               |> assign(:user, user)
+               |> post("/api/v1/statuses/#{activity.id}/mute")
+               |> json_response(200)
+    end
+
+    test "unmute conversation", %{conn: conn, user: user, activity: activity} do
+      {:ok, _} = CommonAPI.add_mute(user, activity)
+
+      id_str = to_string(activity.id)
+      user = refresh_record(user)
+
+      assert %{"id" => ^id_str, "muted" => false} =
+               conn
+               |> assign(:user, user)
+               |> post("/api/v1/statuses/#{activity.id}/unmute")
+               |> json_response(200)
+    end
+  end
 end