diff options
24 files changed, 188 insertions, 42 deletions
| diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b682d70b..de017e30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  ### Added +- Configuration: Added a blacklist for email servers.  - Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation.  - Chats: Added support for federated chats. For details, see the docs.  - ActivityPub: Added support for existing AP ids for instances migrated from Mastodon. diff --git a/config/config.exs b/config/config.exs index fa8051e40..933a899ab 100644 --- a/config/config.exs +++ b/config/config.exs @@ -516,7 +516,8 @@ config :pleroma, Pleroma.User,      "user_exists",      "users",      "web" -  ] +  ], +  email_blacklist: []  config :pleroma, Oban,    repo: Pleroma.Repo, diff --git a/config/description.exs b/config/description.exs index ae2f6d23f..d823812fb 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3056,6 +3056,7 @@ config :pleroma, :config_description, [        %{          key: :restricted_nicknames,          type: {:list, :string}, +        description: "List of nicknames users may not register with.",          suggestions: [            ".well-known",            "~", @@ -3088,6 +3089,12 @@ config :pleroma, :config_description, [            "users",            "web"          ] +      }, +      %{ +        key: :email_blacklist, +        type: {:list, :string}, +        description: "List of email domains users may not register with.", +        suggestions: ["mailinator.com", "maildrop.cc"]        }      ]    }, diff --git a/config/test.exs b/config/test.exs index db0655e73..413c7f0b9 100644 --- a/config/test.exs +++ b/config/test.exs @@ -120,6 +120,8 @@ config :pleroma, Pleroma.Uploaders.S3,  config :tzdata, :autoupdate, :disabled +config :pleroma, :mrf, policies: [] +  if File.exists?("./config/test.secret.exs") do    import_config "test.secret.exs"  else diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 5891fc9b0..f23cf4fe4 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -207,6 +207,11 @@ config :pleroma, :mrf_user_allowlist, %{  * `sign_object_fetches`: Sign object fetches with HTTP signatures  * `authorized_fetch_mode`: Require HTTP signatures for AP fetches +## Pleroma.User + +* `restricted_nicknames`: List of nicknames users may not register with. +* `email_blacklist`: List of email domains users may not register with. +  ## Pleroma.ScheduledActivity  * `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 0c1fab223..09e606b37 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -676,10 +676,19 @@ defmodule Pleroma.User do      |> validate_required([:name, :nickname, :password, :password_confirmation])      |> validate_confirmation(:password)      |> unique_constraint(:email) +    |> validate_format(:email, @email_regex) +    |> validate_change(:email, fn :email, email -> +      valid? = +        Config.get([User, :email_blacklist]) +        |> Enum.all?(fn blacklisted_domain -> +          !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain]) +        end) + +      if valid?, do: [], else: [email: "Invalid email"] +    end)      |> unique_constraint(:nickname)      |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))      |> validate_format(:nickname, local_nickname_regex()) -    |> validate_format(:email, @email_regex)      |> validate_length(:bio, max: bio_limit)      |> validate_length(:name, min: 1, max: name_limit)      |> validate_length(:registration_reason, max: reason_limit) diff --git a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex index 8e47f1e02..7b4c78e0f 100644 --- a/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/activity_expiration_policy.ex @@ -21,8 +21,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy do    @impl true    def describe, do: {:ok, %{}} -  defp local?(%{"id" => id}) do -    String.starts_with?(id, Pleroma.Web.Endpoint.url()) +  defp local?(%{"actor" => actor}) do +    String.starts_with?(actor, Pleroma.Web.Endpoint.url())    end    defp note?(activity) do diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index aeef31945..bd46f8034 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -34,10 +34,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do      cng      |> validate_change(field_name, fn field_name, actor -> -      if User.get_cached_by_ap_id(actor) do -        [] -      else -        [{field_name, "can't find user"}] +      case User.get_cached_by_ap_id(actor) do +        %User{deactivated: true} -> +          [{field_name, "user is deactivated"}] + +        %User{} -> +          [] + +        _ -> +          [{field_name, "can't find user"}]        end      end)    end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 5c7daf1a5..6210f2c5a 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -9,6 +9,11 @@ defmodule Pleroma.Web.RichMedia.Helpers do    alias Pleroma.Object    alias Pleroma.Web.RichMedia.Parser +  @rich_media_options [ +    pool: :media, +    max_body: 2_000_000 +  ] +    @spec validate_page_url(URI.t() | binary()) :: :ok | :error    defp validate_page_url(page_url) when is_binary(page_url) do      validate_tld = Pleroma.Config.get([Pleroma.Formatter, :validate_tld]) @@ -77,4 +82,20 @@ defmodule Pleroma.Web.RichMedia.Helpers do      fetch_data_for_activity(activity)      :ok    end + +  def rich_media_get(url) do +    headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] + +    options = +      if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do +        Keyword.merge(@rich_media_options, +          recv_timeout: 2_000, +          with_body: true +        ) +      else +        @rich_media_options +      end + +    Pleroma.HTTP.get(url, headers, options) +  end  end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index c8a767935..ca592833f 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -3,11 +3,6 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.RichMedia.Parser do -  @options [ -    pool: :media, -    max_body: 2_000_000 -  ] -    defp parsers do      Pleroma.Config.get([:rich_media, :parsers])    end @@ -75,21 +70,8 @@ defmodule Pleroma.Web.RichMedia.Parser do    end    defp parse_url(url) do -    opts = -      if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Hackney do -        Keyword.merge(@options, -          recv_timeout: 2_000, -          with_body: true -        ) -      else -        @options -      end -      try do -      rich_media_agent = Pleroma.Application.user_agent() <> "; Bot" - -      {:ok, %Tesla.Env{body: html}} = -        Pleroma.HTTP.get(url, [{"user-agent", rich_media_agent}], adapter: opts) +      {:ok, %Tesla.Env{body: html}} = Pleroma.Web.RichMedia.Helpers.rich_media_get(url)        html        |> parse_html() diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex index 6bdeac89c..1fe6729c3 100644 --- a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/oembed_parser.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do    end    defp get_oembed_data(url) do -    with {:ok, %Tesla.Env{body: json}} <- Pleroma.HTTP.get(url, [], adapter: [pool: :media]) do +    with {:ok, %Tesla.Env{body: json}} <- Pleroma.Web.RichMedia.Helpers.rich_media_get(url) do        Jason.decode(json)      end    end diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 5836ec1e0..51603fe0c 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -37,7 +37,7 @@        }        a { -        color: color: #d8a070; +        color: #d8a070;          text-decoration: none;        } @@ -214,7 +214,8 @@ defmodule Pleroma.Mixfile do        "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],        "ecto.reset": ["ecto.drop", "ecto.setup"],        test: ["ecto.create --quiet", "ecto.migrate", "test"], -      docs: ["pleroma.docs", "docs"] +      docs: ["pleroma.docs", "docs"], +      analyze: ["credo --strict --only=warnings,todo,fixme,consistency,readability"]      ]    end diff --git a/priv/repo/migrations/20200804180322_remove_nonlocal_expirations.exs b/priv/repo/migrations/20200804180322_remove_nonlocal_expirations.exs new file mode 100644 index 000000000..389935f0d --- /dev/null +++ b/priv/repo/migrations/20200804180322_remove_nonlocal_expirations.exs @@ -0,0 +1,19 @@ +defmodule Pleroma.Repo.Migrations.RemoveNonlocalExpirations do +  use Ecto.Migration + +  def up do +    statement = """ +    DELETE FROM +      activity_expirations A USING activities B +    WHERE +      A.activity_id = B.id +      AND B.local = false; +    """ + +    execute(statement) +  end + +  def down do +    :ok +  end +end diff --git a/priv/repo/migrations/20200804183107_add_unique_index_to_app_client_id.exs b/priv/repo/migrations/20200804183107_add_unique_index_to_app_client_id.exs new file mode 100644 index 000000000..83de18096 --- /dev/null +++ b/priv/repo/migrations/20200804183107_add_unique_index_to_app_client_id.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.AddUniqueIndexToAppClientId do +  use Ecto.Migration + +  def change do +    create(unique_index(:apps, [:client_id])) +  end +end diff --git a/test/tasks/app_test.exs b/test/tasks/app_test.exs index b8f03566d..71a84ac8e 100644 --- a/test/tasks/app_test.exs +++ b/test/tasks/app_test.exs @@ -50,13 +50,13 @@ defmodule Mix.Tasks.Pleroma.AppTest do    defp assert_app(name, redirect, scopes) do      app = Repo.get_by(Pleroma.Web.OAuth.App, client_name: name) -    assert_received {:mix_shell, :info, [message]} +    assert_receive {:mix_shell, :info, [message]}      assert message == "#{name} successfully created:" -    assert_received {:mix_shell, :info, [message]} +    assert_receive {:mix_shell, :info, [message]}      assert message == "App client_id: #{app.client_id}" -    assert_received {:mix_shell, :info, [message]} +    assert_receive {:mix_shell, :info, [message]}      assert message == "App client_secret: #{app.client_secret}"      assert app.scopes == scopes diff --git a/test/user_test.exs b/test/user_test.exs index 2c1f2b7c5..b47405895 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -513,6 +513,29 @@ defmodule Pleroma.UserTest do        refute changeset.valid?      end +    test "it blocks blacklisted email domains" do +      clear_config([User, :email_blacklist], ["trolling.world"]) + +      # Block with match +      params = Map.put(@full_user_data, :email, "troll@trolling.world") +      changeset = User.register_changeset(%User{}, params) +      refute changeset.valid? + +      # Block with subdomain match +      params = Map.put(@full_user_data, :email, "troll@gnomes.trolling.world") +      changeset = User.register_changeset(%User{}, params) +      refute changeset.valid? + +      # Pass with different domains that are similar +      params = Map.put(@full_user_data, :email, "troll@gnomestrolling.world") +      changeset = User.register_changeset(%User{}, params) +      assert changeset.valid? + +      params = Map.put(@full_user_data, :email, "troll@trolling.world.us") +      changeset = User.register_changeset(%User{}, params) +      assert changeset.valid? +    end +      test "it sets the password_hash and ap_id" do        changeset = User.register_changeset(%User{}, @full_user_data) diff --git a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs b/test/web/activity_pub/mrf/activity_expiration_policy_test.exs index 8babf49e7..f25cf8b12 100644 --- a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs +++ b/test/web/activity_pub/mrf/activity_expiration_policy_test.exs @@ -7,11 +7,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do    alias Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy    @id Pleroma.Web.Endpoint.url() <> "/activities/cofe" +  @local_actor Pleroma.Web.Endpoint.url() <> "/users/cofe"    test "adds `expires_at` property" do      assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =               ActivityExpirationPolicy.filter(%{                 "id" => @id, +               "actor" => @local_actor,                 "type" => "Create",                 "object" => %{"type" => "Note"}               }) @@ -25,6 +27,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do      assert {:ok, %{"type" => "Create", "expires_at" => ^expires_at}} =               ActivityExpirationPolicy.filter(%{                 "id" => @id, +               "actor" => @local_actor,                 "type" => "Create",                 "expires_at" => expires_at,                 "object" => %{"type" => "Note"} @@ -37,6 +40,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do      assert {:ok, %{"type" => "Create", "expires_at" => expires_at}} =               ActivityExpirationPolicy.filter(%{                 "id" => @id, +               "actor" => @local_actor,                 "type" => "Create",                 "expires_at" => too_distant_future,                 "object" => %{"type" => "Note"} @@ -49,6 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do      assert {:ok, activity} =               ActivityExpirationPolicy.filter(%{                 "id" => "https://example.com/123", +               "actor" => "https://example.com/users/cofe",                 "type" => "Create",                 "object" => %{"type" => "Note"}               }) @@ -60,6 +65,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do      assert {:ok, activity} =               ActivityExpirationPolicy.filter(%{                 "id" => "https://example.com/123", +               "actor" => "https://example.com/users/cofe",                 "type" => "Follow"               }) @@ -68,6 +74,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicyTest do      assert {:ok, activity} =               ActivityExpirationPolicy.filter(%{                 "id" => "https://example.com/123", +               "actor" => "https://example.com/users/cofe",                 "type" => "Create",                 "object" => %{"type" => "Cofe"}               }) diff --git a/test/web/activity_pub/transmogrifier/chat_message_test.exs b/test/web/activity_pub/transmogrifier/chat_message_test.exs index d6736dc3e..31274c067 100644 --- a/test/web/activity_pub/transmogrifier/chat_message_test.exs +++ b/test/web/activity_pub/transmogrifier/chat_message_test.exs @@ -124,6 +124,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.ChatMessageTest do        {:ok, %Activity{} = _activity} = Transmogrifier.handle_incoming(data)      end +    test "it doesn't work for deactivated users" do +      data = +        File.read!("test/fixtures/create-chat-message.json") +        |> Poison.decode!() + +      _author = +        insert(:user, +          ap_id: data["actor"], +          local: false, +          last_refreshed_at: DateTime.utc_now(), +          deactivated: true +        ) + +      _recipient = insert(:user, ap_id: List.first(data["to"]), local: true) + +      assert {:error, _} = Transmogrifier.handle_incoming(data) +    end +      test "it inserts it and creates a chat" do        data =          File.read!("test/fixtures/create-chat-message.json") diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 7d33feaf2..828964a36 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -163,6 +163,14 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do               end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"      end +    test "it does not work for deactivated users" do +      data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() + +      insert(:user, ap_id: data["actor"], deactivated: true) + +      assert {:error, _} = Transmogrifier.handle_incoming(data) +    end +      test "it works for incoming notices" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 313dda21b..4ba6232dc 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -458,6 +458,11 @@ defmodule Pleroma.Web.CommonAPITest do    end    describe "posting" do +    test "deactivated users can't post" do +      user = insert(:user, deactivated: true) +      assert {:error, _} = CommonAPI.post(user, %{status: "ye"}) +    end +      test "it supports explicit addressing" do        user = insert(:user)        user_two = insert(:user) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index d390c3ce1..17a1e7d66 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -940,17 +940,32 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do        assert refresh        assert scope == "read write follow" +      clear_config([User, :email_blacklist], ["example.org"]) + +      params = %{ +        username: "lain", +        email: "lain@example.org", +        password: "PlzDontHackLain", +        bio: "Test Bio", +        agreement: true +      } +        conn =          build_conn()          |> put_req_header("content-type", "multipart/form-data")          |> put_req_header("authorization", "Bearer " <> token) -        |> post("/api/v1/accounts", %{ -          username: "lain", -          email: "lain@example.org", -          password: "PlzDontHackLain", -          bio: "Test Bio", -          agreement: true -        }) +        |> post("/api/v1/accounts", params) + +      assert %{"error" => "{\"email\":[\"Invalid email\"]}"} = +               json_response_and_validate_schema(conn, 400) + +      Pleroma.Config.put([User, :email_blacklist], []) + +      conn = +        build_conn() +        |> put_req_header("content-type", "multipart/form-data") +        |> put_req_header("authorization", "Bearer " <> token) +        |> post("/api/v1/accounts", params)        %{          "access_token" => token, diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs index c08be37d4..0c5a38bf6 100644 --- a/test/web/mastodon_api/mastodon_api_test.exs +++ b/test/web/mastodon_api/mastodon_api_test.exs @@ -17,8 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do      test "returns error when followed user is deactivated" do        follower = insert(:user)        user = insert(:user, local: true, deactivated: true) -      {:error, error} = MastodonAPI.follow(follower, user) -      assert error == :rejected +      assert {:error, _error} = MastodonAPI.follow(follower, user)      end      test "following for user" do diff --git a/test/web/oauth/app_test.exs b/test/web/oauth/app_test.exs index 899af648e..993a490e0 100644 --- a/test/web/oauth/app_test.exs +++ b/test/web/oauth/app_test.exs @@ -29,5 +29,16 @@ defmodule Pleroma.Web.OAuth.AppTest do        assert exist_app.id == app.id        assert exist_app.scopes == ["read", "write", "follow", "push"]      end + +    test "has unique client_id" do +      insert(:oauth_app, client_name: "", redirect_uris: "", client_id: "boop") + +      error = +        catch_error(insert(:oauth_app, client_name: "", redirect_uris: "", client_id: "boop")) + +      assert %Ecto.ConstraintError{} = error +      assert error.constraint == "apps_client_id_index" +      assert error.type == :unique +    end    end  end | 
