diff --git a/CHANGELOG.md b/CHANGELOG.md index 129c269aa..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. @@ -69,7 +70,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - 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. - "By approval" registrations mode. -- Configuration: Added `:welcome` settings for the welcome message to newly registered users. +- 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.
diff --git a/config/config.exs b/config/config.exs index 857e0afbb..933a899ab 100644 --- a/config/config.exs +++ b/config/config.exs @@ -172,7 +172,7 @@ config :mime, :types, %{ "application/ld+json" => ["activity+json"] } -config :tesla, adapter: Tesla.Adapter.Gun +config :tesla, adapter: Tesla.Adapter.Hackney # Configures http settings, upstream proxy etc. config :pleroma, :http, @@ -261,6 +261,11 @@ config :pleroma, :welcome, sender_nickname: nil, message: nil ], + chat_message: [ + enabled: false, + sender_nickname: nil, + message: nil + ], email: [ enabled: false, sender: nil, @@ -374,6 +379,7 @@ config :pleroma, :mrf_simple, federated_timeline_removal: [], report_removal: [], reject: [], + followers_only: [], accept: [], avatar_removal: [], banner_removal: [], @@ -392,8 +398,9 @@ config :pleroma, :mrf_vocabulary, accept: [], reject: [] +# threshold of 7 days config :pleroma, :mrf_object_age, - threshold: 172_800, + threshold: 604_800, actions: [:delist, :strip_followers] config :pleroma, :rich_media, @@ -509,7 +516,8 @@ config :pleroma, Pleroma.User, "user_exists", "users", "web" - ] + ], + email_blacklist: [] config :pleroma, Oban, repo: Pleroma.Repo, @@ -719,7 +727,7 @@ config :pleroma, :restrict_unauthenticated, config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false config :pleroma, :mrf, - policies: Pleroma.Web.ActivityPub.MRF.NoOpPolicy, + policies: Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, transparency: true, transparency_exclusions: [] diff --git a/config/description.exs b/config/description.exs index 11fbe0d78..d823812fb 100644 --- a/config/description.exs +++ b/config/description.exs @@ -998,6 +998,35 @@ config :pleroma, :config_description, [ ] }, %{ + group: :chat_message, + type: :group, + descpiption: "Chat message settings", + children: [ + %{ + key: :enabled, + type: :boolean, + description: "Enables sends chat message for new user after registration" + }, + %{ + key: :message, + type: :string, + description: + "A message that will be sent to a newly registered users as a chat message", + suggestions: [ + "Hello, welcome on board!" + ] + }, + %{ + key: :sender_nickname, + type: :string, + description: "The nickname of the local user that sends the welcome message", + suggestions: [ + "lain" + ] + } + ] + }, + %{ group: :email, type: :group, descpiption: "Email message settings", @@ -1543,6 +1572,12 @@ config :pleroma, :config_description, [ suggestions: ["example.com", "*.example.com"] }, %{ + key: :followers_only, + type: {:list, :string}, + description: "Force posts from the given instances to be visible by followers only", + suggestions: ["example.com", "*.example.com"] + }, + %{ key: :report_removal, type: {:list, :string}, description: "List of instances to reject reports from", @@ -3021,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", "~", @@ -3053,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/API/pleroma_api.md b/docs/API/pleroma_api.md index b29f4d5a0..4e97d26c0 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -50,7 +50,7 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi * Authentication: not required * Params: none * Response: Provider specific JSON, the only guaranteed parameter is `type` -* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint", seconds_valid: 300}` +* Example response: `{"type": "kocaptcha", "token": "whatever", "url": "https://captcha.kotobank.ch/endpoint", "seconds_valid": 300}` ## `/api/pleroma/delete_account` ### Delete an account diff --git a/docs/clients.md b/docs/clients.md index ea751637e..2a42c659f 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -75,6 +75,13 @@ Feel free to contact us to be added to this list! - Platform: Android, iOS - Features: No Streaming +### Indigenous +- Homepage: +- Source Code: +- Contact: [@realize.be@realize.be](@realize.be@realize.be) +- Platforms: Android +- Features: No Streaming + ## Alternative Web Interfaces ### Brutaldon - Homepage: diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 9c768abef..f23cf4fe4 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -69,6 +69,10 @@ To add configuration to your config file, you can copy it from the base config. * `enabled`: Enables the send a direct message to a newly registered user. Defaults to `false`. * `sender_nickname`: The nickname of the local user that sends the welcome message. * `message`: A message that will be send to a newly registered users as a direct message. +* `chat_message`: - welcome message sent as a chat message. + * `enabled`: Enables the send a chat message to a newly registered user. Defaults to `false`. + * `sender_nickname`: The nickname of the local user that sends the welcome message. + * `message`: A message that will be send to a newly registered users as a chat message. * `email`: - welcome message sent as a email. * `enabled`: Enables the send a welcome email to a newly registered user. Defaults to `false`. * `sender`: The email address or tuple with `{nickname, email}` that will use as sender to the welcome email. @@ -125,6 +129,7 @@ To add configuration to your config file, you can copy it from the base config. * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline. * `reject`: List of instances to reject any activities from. * `accept`: List of instances to accept any activities from. +* `followers_only`: List of instances to decrease post visibility to only the followers, including for DM mentions. * `report_removal`: List of instances to reject reports from. * `avatar_removal`: List of instances to strip avatars from. * `banner_removal`: List of instances to strip banners from. @@ -202,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/docs/configuration/howto_database_config.md b/docs/configuration/howto_database_config.md index 174b08662..9ed4d6cdd 100644 --- a/docs/configuration/howto_database_config.md +++ b/docs/configuration/howto_database_config.md @@ -5,13 +5,7 @@ The configuration of Pleroma has traditionally been managed with a config file, ## Migration to database config -1. Stop your Pleroma instance and edit your Pleroma config to enable database configuration: - - ``` - config :pleroma, configurable_from_database: true - ``` - -2. Run the mix task to migrate to the database. You'll receive some debugging output and a few messages informing you of what happened. +1. Run the mix task to migrate to the database. You'll receive some debugging output and a few messages informing you of what happened. **Source:** @@ -23,6 +17,8 @@ The configuration of Pleroma has traditionally been managed with a config file, **OTP:** + *Note: OTP users need Pleroma to be running for `pleroma_ctl` commands to work* + ``` $ ./bin/pleroma_ctl config migrate_to_db ``` @@ -47,53 +43,56 @@ The configuration of Pleroma has traditionally been managed with a config file, Settings for group :pleroma migrated. ``` - -3. It is recommended to backup your config file now. +2. It is recommended to backup your config file now. + ``` cp config/dev.secret.exs config/dev.secret.exs.orig ``` -4. Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database. +3. Edit your Pleroma config to enable database configuration: - ⚠️ **THIS IS NOT REQUIRED** + ``` + config :pleroma, configurable_from_database: true + ``` + +4. ⚠️ **THIS IS NOT REQUIRED** ⚠️ + + Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres (Repo) and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database. + + Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place. + + A non-exhaustive list of settings that are only possible in the config file include the following: + + * config :pleroma, Pleroma.Web.Endpoint + * config :pleroma, Pleroma.Repo + * config :pleroma, configurable\_from\_database + * config :pleroma, :database, rum_enabled + * config :pleroma, :connections_pool + + Here is an example of a server config stripped down after migration: + + ``` + use Mix.Config + + config :pleroma, Pleroma.Web.Endpoint, + url: [host: "cool.pleroma.site", scheme: "https", port: 443] + + config :pleroma, Pleroma.Repo, + adapter: Ecto.Adapters.Postgres, + username: "pleroma", + password: "MySecretPassword", + database: "pleroma_prod", + hostname: "localhost" + + config :pleroma, configurable_from_database: true + ``` - Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place. - - A non-exhaustive list of settings that are only possible in the config file include the following: - -* config :pleroma, Pleroma.Web.Endpoint -* config :pleroma, Pleroma.Repo -* config :pleroma, configurable_from_database -* config :pleroma, :database, rum_enabled -* config :pleroma, :connections_pool - -Here is an example of a server config stripped down after migration: - -``` -use Mix.Config - -config :pleroma, Pleroma.Web.Endpoint, - url: [host: "cool.pleroma.site", scheme: "https", port: 443] - - -config :pleroma, Pleroma.Repo, - adapter: Ecto.Adapters.Postgres, - username: "pleroma", - password: "MySecretPassword", - database: "pleroma_prod", - hostname: "localhost" - -config :pleroma, configurable_from_database: true -``` - -5. Start your instance back up and you can now access the Settings tab in AdminFE. +5. Restart your instance and you can now access the Settings tab in AdminFE. ## Reverting back from database config -1. Stop your Pleroma instance. - -2. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened. +1. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened. **Source:** @@ -118,7 +117,9 @@ config :pleroma, configurable_from_database: true Database configuration settings have been saved to config/dev.exported_from_db.secret.exs ``` -3. The in-database configuration still exists, but it will not be used if you remove `config :pleroma, configurable_from_database: true` from your config. +2. Remove `config :pleroma, configurable_from_database: true` from your config. The in-database configuration still exists, but it will not be used. Future migrations will erase the database config before importing your config file again. + +3. Restart your instance. ## Debugging diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex index cc80deff5..a8329cc1e 100644 --- a/lib/pleroma/config.ex +++ b/lib/pleroma/config.ex @@ -11,12 +11,10 @@ defmodule Pleroma.Config do def get([key], default), do: get(key, default) - def get([parent_key | keys], default) do - case :pleroma - |> Application.get_env(parent_key) - |> get_in(keys) do - nil -> default - any -> any + def get([_ | _] = path, default) do + case fetch(path) do + {:ok, value} -> value + :error -> default end end @@ -34,6 +32,24 @@ defmodule Pleroma.Config do end end + def fetch(key) when is_atom(key), do: fetch([key]) + + def fetch([root_key | keys]) do + Enum.reduce_while(keys, Application.fetch_env(:pleroma, root_key), fn + key, {:ok, config} when is_map(config) or is_list(config) -> + case Access.fetch(config, key) do + :error -> + {:halt, :error} + + value -> + {:cont, value} + end + + _key, _config -> + {:halt, :error} + end) + end + def put([key], value), do: put(key, value) def put([parent_key | keys], value) do @@ -50,12 +66,15 @@ defmodule Pleroma.Config do def delete([key]), do: delete(key) - def delete([parent_key | keys]) do - {_, parent} = - Application.get_env(:pleroma, parent_key) - |> get_and_update_in(keys, fn _ -> :pop end) + def delete([parent_key | keys] = path) do + with {:ok, _} <- fetch(path) do + {_, parent} = + parent_key + |> get() + |> get_and_update_in(keys, fn _ -> :pop end) - Application.put_env(:pleroma, parent_key, parent) + Application.put_env(:pleroma, parent_key, parent) + end end def delete(key) do diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index c2020d30a..83b366dd4 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -95,7 +95,11 @@ defmodule Pleroma.FollowingRelationship do |> where([r], r.state == ^:follow_accept) end - def followers_ap_ids(%User{} = user, from_ap_ids \\ nil) do + def followers_ap_ids(user, from_ap_ids \\ nil) + + def followers_ap_ids(_, []), do: [] + + def followers_ap_ids(%User{} = user, from_ap_ids) do query = user |> followers_query() diff --git a/lib/pleroma/gun/connection_pool.ex b/lib/pleroma/gun/connection_pool.ex index 49e9885bb..f34602b73 100644 --- a/lib/pleroma/gun/connection_pool.ex +++ b/lib/pleroma/gun/connection_pool.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Gun.ConnectionPool do ] end + @spec get_conn(URI.t(), keyword()) :: {:ok, pid()} | {:error, term()} def get_conn(uri, opts) do key = "#{uri.scheme}:#{uri.host}:#{uri.port}" @@ -54,12 +55,14 @@ defmodule Pleroma.Gun.ConnectionPool do {:DOWN, ^ref, :process, ^worker_pid, reason} -> case reason do - {:shutdown, error} -> error + {:shutdown, {:error, _} = error} -> error + {:shutdown, error} -> {:error, error} _ -> {:error, reason} end end end + @spec release_conn(pid()) :: :ok def release_conn(conn_pid) do # :ets.fun2ms(fn {_, {worker_pid, {gun_pid, _, _, _}}} when gun_pid == conn_pid -> # worker_pid end) diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex index 2fc876d92..8a44a001d 100644 --- a/lib/pleroma/http/request_builder.ex +++ b/lib/pleroma/http/request_builder.ex @@ -34,10 +34,12 @@ defmodule Pleroma.HTTP.RequestBuilder do @spec headers(Request.t(), Request.headers()) :: Request.t() def headers(request, headers) do headers_list = - if Pleroma.Config.get([:http, :send_user_agent]) do + with true <- Pleroma.Config.get([:http, :send_user_agent]), + nil <- Enum.find(headers, fn {key, _val} -> String.downcase(key) == "user-agent" end) do [{"user-agent", Pleroma.Application.user_agent()} | headers] else - headers + _ -> + headers end %{request | headers: headers_list} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 3afd9d8d2..f7f0236b4 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) @@ -734,6 +743,7 @@ defmodule Pleroma.User do {:ok, user} <- set_cache(user), {:ok, _} <- send_welcome_email(user), {:ok, _} <- send_welcome_message(user), + {:ok, _} <- send_welcome_chat_message(user), {:ok, _} <- try_send_confirmation_email(user) do {:ok, user} end @@ -748,6 +758,15 @@ defmodule Pleroma.User do end end + def send_welcome_chat_message(user) do + if User.WelcomeChatMessage.enabled?() do + User.WelcomeChatMessage.post_message(user) + {:ok, :enqueued} + else + {:ok, :noop} + end + end + def send_welcome_email(%User{email: email} = user) when is_binary(email) do if User.WelcomeEmail.enabled?() do User.WelcomeEmail.send_email(user) diff --git a/lib/pleroma/user/welcome_chat_message.ex b/lib/pleroma/user/welcome_chat_message.ex new file mode 100644 index 000000000..3e7d1f424 --- /dev/null +++ b/lib/pleroma/user/welcome_chat_message.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.WelcomeChatMessage do + alias Pleroma.Config + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + @spec enabled?() :: boolean() + def enabled?, do: Config.get([:welcome, :chat_message, :enabled], false) + + @spec post_message(User.t()) :: {:ok, Pleroma.Activity.t() | nil} + def post_message(user) do + [:welcome, :chat_message, :sender_nickname] + |> Config.get(nil) + |> fetch_sender() + |> do_post(user, welcome_message()) + end + + defp do_post(%User{} = sender, recipient, message) + when is_binary(message) do + CommonAPI.post_chat_message( + sender, + recipient, + message + ) + end + + defp do_post(_sender, _recipient, _message), do: {:ok, nil} + + defp fetch_sender(nickname) when is_binary(nickname) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do + user + else + _ -> nil + end + end + + defp fetch_sender(_), do: nil + + defp welcome_message do + Config.get([:welcome, :chat_message, :message], nil) + end +end 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/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index 5f111c72f..d45d2d7e3 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -37,8 +37,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do defp check_delist(message, actions) do if :delist in actions do with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do - to = List.delete(message["to"], Pleroma.Constants.as_public()) ++ [user.follower_address] - cc = List.delete(message["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()] + to = + List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++ + [user.follower_address] + + cc = + List.delete(message["cc"] || [], user.follower_address) ++ + [Pleroma.Constants.as_public()] message = message @@ -58,8 +63,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do defp check_strip_followers(message, actions) do if :strip_followers in actions do with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do - to = List.delete(message["to"], user.follower_address) - cc = List.delete(message["cc"], user.follower_address) + to = List.delete(message["to"] || [], user.follower_address) + cc = List.delete(message["cc"] || [], user.follower_address) message = message diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index b77b8c7b4..bb193475a 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do @behaviour Pleroma.Web.ActivityPub.MRF alias Pleroma.Config + alias Pleroma.FollowingRelationship alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF @@ -108,6 +109,35 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do {:ok, object} end + defp intersection(list1, list2) do + list1 -- list1 -- list2 + end + + defp check_followers_only(%{host: actor_host} = _actor_info, object) do + followers_only = + Config.get([:mrf_simple, :followers_only]) + |> MRF.subdomains_regex() + + object = + with true <- MRF.subdomain_match?(followers_only, actor_host), + user <- User.get_cached_by_ap_id(object["actor"]) do + # Don't use Map.get/3 intentionally, these must not be nil + fixed_to = object["to"] || [] + fixed_cc = object["cc"] || [] + + to = FollowingRelationship.followers_ap_ids(user, fixed_to) + cc = FollowingRelationship.followers_ap_ids(user, fixed_cc) + + object + |> Map.put("to", intersection([user.follower_address | to], fixed_to)) + |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc)) + else + _ -> object + end + + {:ok, object} + end + defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do report_removal = Config.get([:mrf_simple, :report_removal]) @@ -174,6 +204,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do {:ok, object} <- check_media_removal(actor_info, object), {:ok, object} <- check_media_nsfw(actor_info, object), {:ok, object} <- check_ftl_removal(actor_info, object), + {:ok, object} <- check_followers_only(actor_info, object), {:ok, object} <- check_report_removal(actor_info, object) do {:ok, object} else 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/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 50c8e0242..aaebc9b5c 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -449,21 +449,32 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - # TODO: This is actually a token respone, but there's no oauth operation file yet. + # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet. defp create_response do %Schema{ title: "AccountCreateResponse", description: "Response schema for an account", type: :object, properties: %{ + # The response when auto-login on create succeeds (token is issued): token_type: %Schema{type: :string}, access_token: %Schema{type: :string}, refresh_token: %Schema{type: :string}, scope: %Schema{type: :string}, created_at: %Schema{type: :integer, format: :"date-time"}, me: %Schema{type: :string}, - expires_in: %Schema{type: :integer} + expires_in: %Schema{type: :integer}, + # + # The response when registration succeeds but auto-login fails (no token): + identifier: %Schema{type: :string}, + message: %Schema{type: :string} }, + required: [], + # Note: example of successful registration with failed login response: + # example: %{ + # "identifier" => "missing_confirmed_email", + # "message" => "You have been registered. Please check your email for further instructions." + # }, example: %{ "token_type" => "Bearer", "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", diff --git a/lib/pleroma/web/api_spec/schemas/chat_message.ex b/lib/pleroma/web/api_spec/schemas/chat_message.ex index 3ee85aa76..bbf2a4427 100644 --- a/lib/pleroma/web/api_spec/schemas/chat_message.ex +++ b/lib/pleroma/web/api_spec/schemas/chat_message.ex @@ -19,13 +19,46 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ChatMessage do content: %Schema{type: :string, nullable: true}, created_at: %Schema{type: :string, format: :"date-time"}, emojis: %Schema{type: :array}, - attachment: %Schema{type: :object, nullable: true} + attachment: %Schema{type: :object, nullable: true}, + card: %Schema{ + type: :object, + nullable: true, + description: "Preview card for links included within status content", + required: [:url, :title, :description, :type], + properties: %{ + type: %Schema{ + type: :string, + enum: ["link", "photo", "video", "rich"], + description: "The type of the preview card" + }, + provider_name: %Schema{ + type: :string, + nullable: true, + description: "The provider of the original resource" + }, + provider_url: %Schema{ + type: :string, + format: :uri, + description: "A link to the provider of the original resource" + }, + url: %Schema{type: :string, format: :uri, description: "Location of linked resource"}, + image: %Schema{ + type: :string, + nullable: true, + format: :uri, + description: "Preview thumbnail" + }, + title: %Schema{type: :string, description: "Title of linked resource"}, + description: %Schema{type: :string, description: "Description of preview"} + } + } }, example: %{ "account_id" => "someflakeid", "chat_id" => "1", "content" => "hey you again", "created_at" => "2020-04-21T15:06:45.000Z", + "card" => nil, "emojis" => [ %{ "static_url" => "https://dontbulling.me/emoji/Firefox.gif", diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index fe5d022f5..f45678184 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPIController alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthView - alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.TwitterAPI plug(Pleroma.Web.ApiSpec.CastAndValidate) @@ -100,11 +100,34 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do with :ok <- validate_email_param(params), :ok <- TwitterAPI.validate_captcha(app, params), - {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), - {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do + {:ok, user} <- TwitterAPI.register_user(params), + {_, {:ok, token}} <- + {:login, OAuthController.login(user, app, app.scopes)} do json(conn, OAuthView.render("token.json", %{user: user, token: token})) else - {:error, error} -> json_response(conn, :bad_request, %{error: error}) + {:login, {:account_status, :confirmation_pending}} -> + json_response(conn, :ok, %{ + message: "You have been registered. Please check your email for further instructions.", + identifier: "missing_confirmed_email" + }) + + {:login, {:account_status, :approval_pending}} -> + json_response(conn, :ok, %{ + message: + "You have been registered. You'll be able to log in once your account is approved.", + identifier: "awaiting_approval" + }) + + {:login, _} -> + json_response(conn, :ok, %{ + message: + "You have been registered. Some post-registration steps may be pending. " <> + "Please log in manually.", + identifier: "manual_login_required" + }) + + {:error, error} -> + json_response(conn, :bad_request, %{error: error}) end end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 268fcacaf..b16833592 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -271,11 +271,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do ) do with {:ok, %User{} = user} <- Authenticator.get_user(conn), {:ok, app} <- Token.Utils.fetch_app(conn), - {:account_status, :active} <- {:account_status, User.account_status(user)}, - {:ok, scopes} <- validate_scopes(app, params), - {:ok, auth} <- Authorization.create_authorization(app, user, scopes), - {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, - {:ok, token} <- Token.exchange_token(app, auth) do + requested_scopes <- Scopes.fetch_scopes(params, app.scopes), + {:ok, token} <- login(user, app, requested_scopes) do json(conn, OAuthView.render("token.json", %{user: user, token: token})) else error -> @@ -533,6 +530,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end + defp do_create_authorization(conn, auth_attrs, user \\ nil) + defp do_create_authorization( %Plug.Conn{} = conn, %{ @@ -542,19 +541,37 @@ defmodule Pleroma.Web.OAuth.OAuthController do "redirect_uri" => redirect_uri } = auth_attrs }, - user \\ nil + user ) do with {_, {:ok, %User{} = user}} <- {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)}, %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), - {:ok, scopes} <- validate_scopes(app, auth_attrs), - {:account_status, :active} <- {:account_status, User.account_status(user)}, - {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do + requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes), + {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do {:ok, auth, user} end end + defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes) + when is_list(requested_scopes) do + with {:account_status, :active} <- {:account_status, User.account_status(user)}, + {:ok, scopes} <- validate_scopes(app, requested_scopes), + {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do + {:ok, auth} + end + end + + # Note: intended to be a private function but opened for AccountController that logs in on signup + @doc "If checks pass, creates authorization and token for given user, app and requested scopes." + def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do + with {:ok, auth} <- do_create_authorization(user, app, requested_scopes), + {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)}, + {:ok, token} <- Token.exchange_token(app, auth) do + {:ok, token} + end + end + # Special case: Local MastodonFE defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login) @@ -571,12 +588,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - @spec validate_scopes(App.t(), map()) :: + @spec validate_scopes(App.t(), map() | list()) :: {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} - defp validate_scopes(%App{} = app, params) do - params - |> Scopes.fetch_scopes(app.scopes) - |> Scopes.validate(app.scopes) + defp validate_scopes(%App{} = app, params) when is_map(params) do + requested_scopes = Scopes.fetch_scopes(params, app.scopes) + validate_scopes(app, requested_scopes) + end + + defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do + Scopes.validate(requested_scopes, app.scopes) end def default_redirect_uri(%App{} = app) do diff --git a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex index f2112a86e..d4e08b50d 100644 --- a/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex +++ b/lib/pleroma/web/pleroma_api/views/chat/message_reference_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do %{ chat_message_reference: %{ id: id, - object: %{data: chat_message}, + object: %{data: chat_message} = object, chat_id: chat_id, unread: unread } @@ -30,7 +30,12 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceView do attachment: chat_message["attachment"] && StatusView.render("attachment.json", attachment: chat_message["attachment"]), - unread: unread + unread: unread, + card: + StatusView.render( + "card.json", + Pleroma.Web.RichMedia.Helpers.fetch_data_for_object(object) + ) } end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 747f2dc6b..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]) @@ -49,11 +54,11 @@ defmodule Pleroma.Web.RichMedia.Helpers do |> hd end - def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do + def fetch_data_for_object(object) do with true <- Config.get([:rich_media, :enabled]), - %Object{} = object <- Object.normalize(activity), false <- object.data["sensitive"] || false, - {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]), + {:ok, page_url} <- + HTML.extract_first_external_url(object, object.data["content"]), :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do %{page_url: page_url, rich_media: rich_media} @@ -62,10 +67,35 @@ defmodule Pleroma.Web.RichMedia.Helpers do end end + def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do + with true <- Config.get([:rich_media, :enabled]), + %Object{} = object <- Object.normalize(activity) do + fetch_data_for_object(object) + else + _ -> %{} + end + end + def fetch_data_for_activity(_), do: %{} def perform(:fetch, %Activity{} = activity) 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/mix.exs b/mix.exs index 860c6aee7..63142dee7 100644 --- a/mix.exs +++ b/mix.exs @@ -178,7 +178,7 @@ defmodule Pleroma.Mixfile do {:flake_id, "~> 0.1.0"}, {:concurrent_limiter, git: "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", - ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"}, + ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"}, {:remote_ip, git: "https://git.pleroma.social/pleroma/remote_ip.git", ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"}, @@ -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/mix.lock b/mix.lock index 17b11cdb2..55c3c59c6 100644 --- a/mix.lock +++ b/mix.lock @@ -14,7 +14,7 @@ "certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, - "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "8eee96c6ba39b9286ec44c51c52d9f2758951365", [ref: "8eee96c6ba39b9286ec44c51c52d9f2758951365"]}, + "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "55e92f84b4ed531bd487952a71040a9c69dc2807", [ref: "55e92f84b4ed531bd487952a71040a9c69dc2807"]}, "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, "cors_plug": {:hex, :cors_plug, "2.0.2", "2b46083af45e4bc79632bd951550509395935d3e7973275b2b743bd63cc942ce", [:mix], [{:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0d0e13f71c51fd4ef8b2c7e051388e4dfb267522a83a22392c856de7e46465f"}, "cowboy": {:hex, :cowboy, "2.8.0", "f3dc62e35797ecd9ac1b50db74611193c29815401e53bac9a5c0577bd7bc667d", [:rebar3], [{:cowlib, "~> 2.9.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "4643e4fba74ac96d4d152c75803de6fad0b3fa5df354c71afdd6cbeeb15fac8a"}, diff --git a/priv/repo/migrations/20200802170532_fix_legacy_tags.exs b/priv/repo/migrations/20200802170532_fix_legacy_tags.exs new file mode 100644 index 000000000..f7274b44e --- /dev/null +++ b/priv/repo/migrations/20200802170532_fix_legacy_tags.exs @@ -0,0 +1,37 @@ +# Fix legacy tags set by AdminFE that don't align with TagPolicy MRF + +defmodule Pleroma.Repo.Migrations.FixLegacyTags do + use Ecto.Migration + alias Pleroma.Repo + alias Pleroma.User + import Ecto.Query + + @old_new_map %{ + "force_nsfw" => "mrf_tag:media-force-nsfw", + "strip_media" => "mrf_tag:media-strip", + "force_unlisted" => "mrf_tag:force-unlisted", + "sandbox" => "mrf_tag:sandbox", + "disable_remote_subscription" => "mrf_tag:disable-remote-subscription", + "disable_any_subscription" => "mrf_tag:disable-any-subscription" + } + + def change do + legacy_tags = Map.keys(@old_new_map) + + from(u in User, where: fragment("? && ?", u.tags, ^legacy_tags)) + |> Repo.all() + |> Enum.each(fn user -> + fix_tags_changeset(user) + |> Repo.update() + end) + end + + defp fix_tags_changeset(%User{tags: tags} = user) do + new_tags = + Enum.map(tags, fn tag -> + Map.get(@old_new_map, tag, tag) + end) + + Ecto.Changeset.change(user, tags: new_tags) + end +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/config_test.exs b/test/config_test.exs index a46ab4302..1556e4237 100644 --- a/test/config_test.exs +++ b/test/config_test.exs @@ -28,6 +28,34 @@ defmodule Pleroma.ConfigTest do assert Pleroma.Config.get([:azerty, :uiop], true) == true end + describe "nil values" do + setup do + Pleroma.Config.put(:lorem, nil) + Pleroma.Config.put(:ipsum, %{dolor: [sit: nil]}) + Pleroma.Config.put(:dolor, sit: %{amet: nil}) + + on_exit(fn -> Enum.each(~w(lorem ipsum dolor)a, &Pleroma.Config.delete/1) end) + end + + test "get/1 with an atom for nil value" do + assert Pleroma.Config.get(:lorem) == nil + end + + test "get/2 with an atom for nil value" do + assert Pleroma.Config.get(:lorem, true) == nil + end + + test "get/1 with a list of keys for nil value" do + assert Pleroma.Config.get([:ipsum, :dolor, :sit]) == nil + assert Pleroma.Config.get([:dolor, :sit, :amet]) == nil + end + + test "get/2 with a list of keys for nil value" do + assert Pleroma.Config.get([:ipsum, :dolor, :sit], true) == nil + assert Pleroma.Config.get([:dolor, :sit, :amet], true) == nil + end + end + test "get/1 when value is false" do Pleroma.Config.put([:instance, :false_test], false) Pleroma.Config.put([:instance, :nested], []) @@ -89,5 +117,23 @@ defmodule Pleroma.ConfigTest do Pleroma.Config.put([:delete_me, :delete_me], hello: "world", world: "Hello") Pleroma.Config.delete([:delete_me, :delete_me, :world]) assert Pleroma.Config.get([:delete_me, :delete_me]) == [hello: "world"] + + assert Pleroma.Config.delete([:this_key_does_not_exist]) + assert Pleroma.Config.delete([:non, :existing, :key]) + end + + test "fetch/1" do + Pleroma.Config.put([:lorem], :ipsum) + Pleroma.Config.put([:ipsum], dolor: :sit) + + assert Pleroma.Config.fetch([:lorem]) == {:ok, :ipsum} + assert Pleroma.Config.fetch(:lorem) == {:ok, :ipsum} + assert Pleroma.Config.fetch([:ipsum, :dolor]) == {:ok, :sit} + assert Pleroma.Config.fetch([:lorem, :ipsum]) == :error + assert Pleroma.Config.fetch([:loremipsum]) == :error + assert Pleroma.Config.fetch(:loremipsum) == :error + + Pleroma.Config.delete([:lorem]) + Pleroma.Config.delete([:ipsum]) end end diff --git a/test/migrations/20200802170532_fix_legacy_tags_test.exs b/test/migrations/20200802170532_fix_legacy_tags_test.exs new file mode 100644 index 000000000..3b4dee407 --- /dev/null +++ b/test/migrations/20200802170532_fix_legacy_tags_test.exs @@ -0,0 +1,24 @@ +defmodule Pleroma.Repo.Migrations.FixLegacyTagsTest do + alias Pleroma.User + use Pleroma.DataCase + import Pleroma.Factory + import Pleroma.Tests.Helpers + + setup_all do: require_migration("20200802170532_fix_legacy_tags") + + test "change/0 converts legacy user tags into correct values", %{migration: migration} do + user = insert(:user, tags: ["force_nsfw", "force_unlisted", "verified"]) + user2 = insert(:user) + + assert :ok == migration.change() + + fixed_user = User.get_by_id(user.id) + fixed_user2 = User.get_by_id(user2.id) + + assert fixed_user.tags == ["mrf_tag:media-force-nsfw", "mrf_tag:force-unlisted", "verified"] + assert fixed_user2.tags == [] + + # user2 should not have been updated + assert fixed_user2.updated_at == fixed_user2.inserted_at + end +end diff --git a/test/report_note_test.exs b/test/report_note_test.exs new file mode 100644 index 000000000..25c1d6a61 --- /dev/null +++ b/test/report_note_test.exs @@ -0,0 +1,16 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ReportNoteTest do + alias Pleroma.ReportNote + use Pleroma.DataCase + import Pleroma.Factory + + test "create/3" do + user = insert(:user) + report = insert(:report_activity) + assert {:ok, note} = ReportNote.create(user.id, report.id, "naughty boy") + assert note.content == "naughty boy" + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index 635d83650..486eda8da 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -297,6 +297,30 @@ defmodule Pleroma.Factory do } end + def report_activity_factory(attrs \\ %{}) do + user = attrs[:user] || insert(:user) + activity = attrs[:activity] || insert(:note_activity) + state = attrs[:state] || "open" + + data = %{ + "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), + "actor" => user.ap_id, + "type" => "Flag", + "object" => [activity.actor, activity.data["id"]], + "published" => DateTime.utc_now() |> DateTime.to_iso8601(), + "to" => [], + "cc" => [activity.actor], + "context" => activity.data["context"], + "state" => state + } + + %Pleroma.Activity{ + data: data, + actor: data["actor"], + recipients: data["to"] ++ data["cc"] + } + end + def oauth_app_factory do %Pleroma.Web.OAuth.App{ client_name: sequence(:client_name, &"Some client #{&1}"), diff --git a/test/support/helpers.ex b/test/support/helpers.ex index 5cbf2e291..ecd4b1e18 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -17,9 +17,19 @@ defmodule Pleroma.Tests.Helpers do defmacro clear_config(config_path, do: yield) do quote do - initial_setting = Config.get(unquote(config_path)) + initial_setting = Config.fetch(unquote(config_path)) unquote(yield) - on_exit(fn -> Config.put(unquote(config_path), initial_setting) end) + + on_exit(fn -> + case initial_setting do + :error -> + Config.delete(unquote(config_path)) + + {:ok, value} -> + Config.put(unquote(config_path), value) + end + end) + :ok 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/welcome_chat_massage_test.exs b/test/user/welcome_chat_massage_test.exs new file mode 100644 index 000000000..fe26d6e4d --- /dev/null +++ b/test/user/welcome_chat_massage_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.WelcomeChatMessageTest do + use Pleroma.DataCase + + alias Pleroma.Config + alias Pleroma.User.WelcomeChatMessage + + import Pleroma.Factory + + setup do: clear_config([:welcome]) + + describe "post_message/1" do + test "send a chat welcome message" do + welcome_user = insert(:user, name: "mewmew") + user = insert(:user) + + Config.put([:welcome, :chat_message, :enabled], true) + Config.put([:welcome, :chat_message, :sender_nickname], welcome_user.nickname) + + Config.put( + [:welcome, :chat_message, :message], + "Hello, welcome to Blob/Cat!" + ) + + {:ok, %Pleroma.Activity{} = activity} = WelcomeChatMessage.post_message(user) + + assert user.ap_id in activity.recipients + assert Pleroma.Object.normalize(activity).data["type"] == "ChatMessage" + assert Pleroma.Object.normalize(activity).data["content"] == "Hello, welcome to Blob/Cat!" + end + end +end diff --git a/test/user_test.exs b/test/user_test.exs index 86ea4e5b9..27bf0ce3f 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -412,8 +412,36 @@ defmodule Pleroma.UserTest do welcome_user = insert(:user) Pleroma.Config.put([:welcome, :direct_message, :enabled], true) Pleroma.Config.put([:welcome, :direct_message, :sender_nickname], welcome_user.nickname) - Pleroma.Config.put([:welcome, :direct_message, :message], "Hello, this is a cool site") + Pleroma.Config.put([:welcome, :direct_message, :message], "Hello, this is a direct message") + cng = User.register_changeset(%User{}, @full_user_data) + {:ok, registered_user} = User.register(cng) + ObanHelpers.perform_all() + + activity = Repo.one(Pleroma.Activity) + assert registered_user.ap_id in activity.recipients + assert Object.normalize(activity).data["content"] =~ "direct message" + assert activity.actor == welcome_user.ap_id + end + + test "it sends a welcome chat message if it is set" do + welcome_user = insert(:user) + Pleroma.Config.put([:welcome, :chat_message, :enabled], true) + Pleroma.Config.put([:welcome, :chat_message, :sender_nickname], welcome_user.nickname) + Pleroma.Config.put([:welcome, :chat_message, :message], "Hello, this is a chat message") + + cng = User.register_changeset(%User{}, @full_user_data) + {:ok, registered_user} = User.register(cng) + ObanHelpers.perform_all() + + activity = Repo.one(Pleroma.Activity) + assert registered_user.ap_id in activity.recipients + assert Object.normalize(activity).data["content"] =~ "chat message" + assert activity.actor == welcome_user.ap_id + end + + test "it sends a welcome email message if it is set" do + welcome_user = insert(:user) Pleroma.Config.put([:welcome, :email, :enabled], true) Pleroma.Config.put([:welcome, :email, :sender], welcome_user.email) @@ -428,11 +456,6 @@ defmodule Pleroma.UserTest do {:ok, registered_user} = User.register(cng) ObanHelpers.perform_all() - activity = Repo.one(Pleroma.Activity) - assert registered_user.ap_id in activity.recipients - assert Object.normalize(activity).data["content"] =~ "cool site" - assert activity.actor == welcome_user.ap_id - assert_email_sent( from: {instance_name, welcome_user.email}, to: {registered_user.name, registered_user.email}, @@ -490,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) @@ -500,6 +546,24 @@ defmodule Pleroma.UserTest do assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers" end + + test "it sets the 'accepts_chat_messages' set to true" do + changeset = User.register_changeset(%User{}, @full_user_data) + assert changeset.valid? + + {:ok, user} = Repo.insert(changeset) + + assert user.accepts_chat_messages + end + + test "it creates a confirmed user" do + changeset = User.register_changeset(%User{}, @full_user_data) + assert changeset.valid? + + {:ok, user} = Repo.insert(changeset) + + refute user.confirmation_pending + end end describe "user registration, with :account_activation_required" do @@ -513,15 +577,6 @@ defmodule Pleroma.UserTest do } setup do: clear_config([:instance, :account_activation_required], true) - test "it sets the 'accepts_chat_messages' set to true" do - changeset = User.register_changeset(%User{}, @full_user_data) - assert changeset.valid? - - {:ok, user} = Repo.insert(changeset) - - assert user.accepts_chat_messages - end - test "it creates unconfirmed user" do changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? 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/mrf/object_age_policy_test.exs b/test/web/activity_pub/mrf/object_age_policy_test.exs index b0fb753bd..cf6acc9a2 100644 --- a/test/web/activity_pub/mrf/object_age_policy_test.exs +++ b/test/web/activity_pub/mrf/object_age_policy_test.exs @@ -38,6 +38,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do end describe "with reject action" do + test "works with objects with empty to or cc fields" do + Config.put([:mrf_object_age, :actions], [:reject]) + + data = + get_old_message() + |> Map.put("cc", nil) + |> Map.put("to", nil) + + assert match?({:reject, _}, ObjectAgePolicy.filter(data)) + end + test "it rejects an old post" do Config.put([:mrf_object_age, :actions], [:reject]) @@ -56,6 +67,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do end describe "with delist action" do + test "works with objects with empty to or cc fields" do + Config.put([:mrf_object_age, :actions], [:delist]) + + data = + get_old_message() + |> Map.put("cc", nil) + |> Map.put("to", nil) + + {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, data} = ObjectAgePolicy.filter(data) + + assert Visibility.get_visibility(%{data: data}) == "unlisted" + end + test "it delists an old post" do Config.put([:mrf_object_age, :actions], [:delist]) @@ -80,6 +106,22 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do end describe "with strip_followers action" do + test "works with objects with empty to or cc fields" do + Config.put([:mrf_object_age, :actions], [:strip_followers]) + + data = + get_old_message() + |> Map.put("cc", nil) + |> Map.put("to", nil) + + {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"]) + + {:ok, data} = ObjectAgePolicy.filter(data) + + refute user.follower_address in data["to"] + refute user.follower_address in data["cc"] + end + test "it strips followers collections from an old post" do Config.put([:mrf_object_age, :actions], [:strip_followers]) diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index e842d8d8d..d7dde62c4 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do import Pleroma.Factory alias Pleroma.Config alias Pleroma.Web.ActivityPub.MRF.SimplePolicy + alias Pleroma.Web.CommonAPI setup do: clear_config(:mrf_simple, @@ -15,6 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do federated_timeline_removal: [], report_removal: [], reject: [], + followers_only: [], accept: [], avatar_removal: [], banner_removal: [], @@ -261,6 +263,64 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do end end + describe "when :followers_only" do + test "is empty" do + Config.put([:mrf_simple, :followers_only], []) + {_, ftl_message} = build_ftl_actor_and_message() + local_message = build_local_message() + + assert SimplePolicy.filter(ftl_message) == {:ok, ftl_message} + assert SimplePolicy.filter(local_message) == {:ok, local_message} + end + + test "has a matching host" do + actor = insert(:user) + following_user = insert(:user) + non_following_user = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(following_user, actor) + + activity = %{ + "actor" => actor.ap_id, + "to" => [ + "https://www.w3.org/ns/activitystreams#Public", + following_user.ap_id, + non_following_user.ap_id + ], + "cc" => [actor.follower_address, "http://foo.bar/qux"] + } + + dm_activity = %{ + "actor" => actor.ap_id, + "to" => [ + following_user.ap_id, + non_following_user.ap_id + ], + "cc" => [] + } + + actor_domain = + activity + |> Map.fetch!("actor") + |> URI.parse() + |> Map.fetch!(:host) + + Config.put([:mrf_simple, :followers_only], [actor_domain]) + + assert {:ok, new_activity} = SimplePolicy.filter(activity) + assert actor.follower_address in new_activity["cc"] + assert following_user.ap_id in new_activity["to"] + refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["to"] + refute "https://www.w3.org/ns/activitystreams#Public" in new_activity["cc"] + refute non_following_user.ap_id in new_activity["to"] + refute non_following_user.ap_id in new_activity["cc"] + + assert {:ok, new_dm_activity} = SimplePolicy.filter(dm_activity) + assert new_dm_activity["to"] == [following_user.ap_id] + assert new_dm_activity["cc"] == [] + end + end + describe "when :accept" do test "is empty" do Config.put([:mrf_simple, :accept], []) 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 e6b283aab..17a1e7d66 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -5,7 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Config alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -16,8 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do import Pleroma.Factory describe "account fetching" do - setup do: clear_config([:instance, :limit_to_local_content]) - test "works by id" do %User{id: user_id} = insert(:user) @@ -42,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do end test "works by nickname for remote users" do - Config.put([:instance, :limit_to_local_content], false) + clear_config([:instance, :limit_to_local_content], false) user = insert(:user, nickname: "user@example.com", local: false) @@ -53,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do end test "respects limit_to_local_content == :all for remote user nicknames" do - Config.put([:instance, :limit_to_local_content], :all) + clear_config([:instance, :limit_to_local_content], :all) user = insert(:user, nickname: "user@example.com", local: false) @@ -63,7 +60,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do end test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do - Config.put([:instance, :limit_to_local_content], :unauthenticated) + clear_config([:instance, :limit_to_local_content], :unauthenticated) user = insert(:user, nickname: "user@example.com", local: false) reading_user = insert(:user) @@ -903,10 +900,93 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do [valid_params: valid_params] end - setup do: clear_config([:instance, :account_activation_required]) - setup do: clear_config([:instance, :account_approval_required]) + test "registers and logs in without :account_activation_required / :account_approval_required", + %{conn: conn} do + clear_config([:instance, :account_activation_required], false) + clear_config([:instance, :account_approval_required], false) + + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/apps", %{ + client_name: "client_name", + redirect_uris: "urn:ietf:wg:oauth:2.0:oob", + scopes: "read, write, follow" + }) + + assert %{ + "client_id" => client_id, + "client_secret" => client_secret, + "id" => _, + "name" => "client_name", + "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", + "vapid_key" => _, + "website" => nil + } = json_response_and_validate_schema(conn, 200) + + conn = + post(conn, "/oauth/token", %{ + grant_type: "client_credentials", + client_id: client_id, + client_secret: client_secret + }) + + assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = + json_response(conn, 200) + + assert token + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + 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", 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, + "created_at" => _created_at, + "scope" => ^scope, + "token_type" => "Bearer" + } = json_response_and_validate_schema(conn, 200) + + token_from_db = Repo.get_by(Token, token: token) + assert token_from_db + user = Repo.preload(token_from_db, :user).user + + assert user + refute user.confirmation_pending + refute user.approval_pending + end + + test "registers but does not log in with :account_activation_required", %{conn: conn} do + clear_config([:instance, :account_activation_required], true) + clear_config([:instance, :account_approval_required], false) - test "Account registration via Application", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/json") @@ -954,23 +1034,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do agreement: true }) - %{ - "access_token" => token, - "created_at" => _created_at, - "scope" => ^scope, - "token_type" => "Bearer" - } = json_response_and_validate_schema(conn, 200) + response = json_response_and_validate_schema(conn, 200) + assert %{"identifier" => "missing_confirmed_email"} = response + refute response["access_token"] + refute response["token_type"] - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user - - assert token_from_db.user.confirmation_pending + user = Repo.get_by(User, email: "lain@example.org") + assert user.confirmation_pending end - test "Account registration via app with account_approval_required", %{conn: conn} do - Pleroma.Config.put([:instance, :account_approval_required], true) + test "registers but does not log in with :account_approval_required", %{conn: conn} do + clear_config([:instance, :account_approval_required], true) + clear_config([:instance, :account_activation_required], false) conn = conn @@ -1020,22 +1095,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do reason: "I'm a cool dude, bro" }) - %{ - "access_token" => token, - "created_at" => _created_at, - "scope" => ^scope, - "token_type" => "Bearer" - } = json_response_and_validate_schema(conn, 200) + response = json_response_and_validate_schema(conn, 200) + assert %{"identifier" => "awaiting_approval"} = response + refute response["access_token"] + refute response["token_type"] - token_from_db = Repo.get_by(Token, token: token) - assert token_from_db - token_from_db = Repo.preload(token_from_db, :user) - assert token_from_db.user + user = Repo.get_by(User, email: "lain@example.org") - assert token_from_db.user.confirmation_pending - assert token_from_db.user.approval_pending - - assert token_from_db.user.registration_reason == "I'm a cool dude, bro" + assert user.approval_pending + assert user.registration_reason == "I'm a cool dude, bro" end test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do @@ -1089,11 +1157,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do end) end - setup do: clear_config([:instance, :account_activation_required]) - test "returns bad_request if missing email params when :account_activation_required is enabled", %{conn: conn, valid_params: valid_params} do - Pleroma.Config.put([:instance, :account_activation_required], true) + clear_config([:instance, :account_activation_required], true) app_token = insert(:oauth_token, user: nil) @@ -1258,8 +1324,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert token_from_db token_from_db = Repo.preload(token_from_db, :user) assert token_from_db.user - - assert token_from_db.user.confirmation_pending end conn = 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 diff --git a/test/web/pleroma_api/views/chat/message_reference_view_test.exs b/test/web/pleroma_api/views/chat/message_reference_view_test.exs index e5b165255..40dbae3cd 100644 --- a/test/web/pleroma_api/views/chat/message_reference_view_test.exs +++ b/test/web/pleroma_api/views/chat/message_reference_view_test.exs @@ -43,7 +43,17 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceViewTest do assert chat_message[:unread] == false assert match?([%{shortcode: "firefox"}], chat_message[:emojis]) - {:ok, activity} = CommonAPI.post_chat_message(recipient, user, "gkgkgk", media_id: upload.id) + clear_config([:rich_media, :enabled], true) + + Tesla.Mock.mock(fn + %{url: "https://example.com/ogp"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")} + end) + + {:ok, activity} = + CommonAPI.post_chat_message(recipient, user, "gkgkgk https://example.com/ogp", + media_id: upload.id + ) object = Object.normalize(activity) @@ -52,10 +62,11 @@ defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceViewTest do chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref) assert chat_message_two[:id] == cm_ref.id - assert chat_message_two[:content] == "gkgkgk" + assert chat_message_two[:content] == object.data["content"] assert chat_message_two[:account_id] == recipient.id assert chat_message_two[:chat_id] == chat_message[:chat_id] assert chat_message_two[:attachment] assert chat_message_two[:unread] == true + assert chat_message_two[:card] end end