diff --git a/CHANGELOG.md b/CHANGELOG.md index e1697271d..66b32e237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,15 +15,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking:** removed `with_move` parameter from notifications timeline. ### Added +- Instance: Extend `/api/v1/instance` with Pleroma-specific information. - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. - NodeInfo: `pleroma_emoji_reactions` to the `features` list. - Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. - New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required. - Mix task to create trusted OAuth App. -- Notifications: Added `follow_request` notification type (configurable, see `[:notifications, :enable_follow_request_notifications]` setting). +- Notifications: Added `follow_request` notification type. - Added `:reject_deletes` group to SimplePolicy
API Changes +- Mastodon API: Extended `/api/v1/instance`. - Mastodon API: Support for `include_types` in `/api/v1/notifications`. - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint. - Mastodon API: Add support for filtering replies in public and home timelines @@ -37,6 +39,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Filtering of push notifications on activities from blocked domains ## [unreleased-patch] +### Security +- Disallow re-registration of previously deleted users, which allowed viewing direct messages addressed to them +- Mastodon API: Fix `POST /api/v1/follow_requests/:id/authorize` allowing to force a follow from a local user even if they didn't request to follow + ### Fixed - Logger configuration through AdminFE - HTTP Basic Authentication permissions issue diff --git a/config/config.exs b/config/config.exs index 2e538c4be..a6c6d6f99 100644 --- a/config/config.exs +++ b/config/config.exs @@ -562,8 +562,6 @@ inactivity_threshold: 7 } -config :pleroma, :notifications, enable_follow_request_notifications: false - config :pleroma, :oauth2, token_expires_in: 600, issue_new_refresh_token: true, diff --git a/config/description.exs b/config/description.exs index 7fac1e561..9d8e3b93c 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2273,20 +2273,6 @@ } ] }, - %{ - group: :pleroma, - key: :notifications, - type: :group, - description: "Notification settings", - children: [ - %{ - key: :enable_follow_request_notifications, - type: :boolean, - description: - "Enables notifications on new follow requests (causes issues with older PleromaFE versions)." - } - ] - }, %{ group: :pleroma, key: Pleroma.Emails.UserEmail, diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index a62719c5d..8d1da936f 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -202,8 +202,22 @@ Has theses additional parameters (which are the same as in Pleroma-API): - `bio`: optional - `captcha_solution`: optional, contains provider-specific captcha solution, - `captcha_token`: optional, contains provider-specific captcha token +- `captcha_answer_data`: optional, contains provider-specific captcha data - `token`: invite token required when the registrations aren't public. +## Instance + +`GET /api/v1/instance` has additional fields + +- `max_toot_chars`: The maximum characters per post +- `poll_limits`: The limits of polls +- `upload_limit`: The maximum upload file size +- `avatar_upload_limit`: The same for avatars +- `background_upload_limit`: The same for backgrounds +- `banner_upload_limit`: The same for banners +- `pleroma.metadata.features`: A list of supported features +- `pleroma.metadata.federation`: The federation restrictions of this instance +- `vapid_public_key`: The public key needed for push messages ## Markers diff --git a/docs/clients.md b/docs/clients.md index 1eae0f0c6..7f98dc7b1 100644 --- a/docs/clients.md +++ b/docs/clients.md @@ -49,11 +49,11 @@ Feel free to contact us to be added to this list! - Platforms: Android - Features: Streaming Ready -### Roma -- Homepage: -- Source Code: [iOS](https://github.com/roma-apps/roma-ios), [Android](https://github.com/roma-apps/roma-android) +### Fedi +- Homepage: +- Source Code: Proprietary, but free - Platforms: iOS, Android -- Features: No Streaming +- Features: Pleroma-specific features like Reactions ### Tusky - Homepage: diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index a00938c04..308d8cffa 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -73,7 +73,6 @@ def start(_type, _args) do Pleroma.Repo, Config.TransferTask, Pleroma.Emoji, - Pleroma.Captcha, Pleroma.Plugs.RateLimiter.Supervisor ] ++ cachex_children() ++ diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index cf75c3adc..6ab754b6f 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -3,53 +3,22 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha do - import Pleroma.Web.Gettext - alias Calendar.DateTime alias Plug.Crypto.KeyGenerator alias Plug.Crypto.MessageEncryptor - use GenServer - - @doc false - def start_link(_) do - GenServer.start_link(__MODULE__, [], name: __MODULE__) - end - - @doc false - def init(_) do - {:ok, nil} - end - @doc """ Ask the configured captcha service for a new captcha """ def new do - GenServer.call(__MODULE__, :new) - end - - @doc """ - Ask the configured captcha service to validate the captcha - """ - def validate(token, captcha, answer_data) do - GenServer.call(__MODULE__, {:validate, token, captcha, answer_data}) - end - - @doc false - def handle_call(:new, _from, state) do - enabled = Pleroma.Config.get([__MODULE__, :enabled]) - - if !enabled do - {:reply, %{type: :none}, state} + if not enabled?() do + %{type: :none} else new_captcha = method().new() - secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) - # This make salt a little different for two keys - token = new_captcha[:token] - secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") - sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + {secret, sign_secret} = secret_pair(new_captcha[:token]) + # Basically copy what Phoenix.Token does here, add the time to # the actual data and make it a binary to then encrypt it encrypted_captcha_answer = @@ -60,55 +29,73 @@ def handle_call(:new, _from, state) do |> :erlang.term_to_binary() |> MessageEncryptor.encrypt(secret, sign_secret) - { - :reply, - # Replace the answer with the encrypted answer - %{new_captcha | answer_data: encrypted_captcha_answer}, - state - } + # Replace the answer with the encrypted answer + %{new_captcha | answer_data: encrypted_captcha_answer} end end - @doc false - def handle_call({:validate, token, captcha, answer_data}, _from, state) do + @doc """ + Ask the configured captcha service to validate the captcha + """ + def validate(token, captcha, answer_data) do + with {:ok, %{at: at, answer_data: answer_md5}} <- validate_answer_data(token, answer_data), + :ok <- validate_expiration(at), + :ok <- validate_usage(token), + :ok <- method().validate(token, captcha, answer_md5), + {:ok, _} <- mark_captcha_as_used(token) do + :ok + end + end + + def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], false) + + defp seconds_valid, do: Pleroma.Config.get!([__MODULE__, :seconds_valid]) + + defp secret_pair(token) do secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + {secret, sign_secret} + end + + defp validate_answer_data(token, answer_data) do + {secret, sign_secret} = secret_pair(token) + + with false <- is_nil(answer_data), + {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), + %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do + {:ok, %{at: at, answer_data: answer_md5}} + else + _ -> {:error, :invalid_answer_data} + end + end + + defp validate_expiration(created_at) do # If the time found is less than (current_time-seconds_valid) then the time has already passed # Later we check that the time found is more than the presumed invalidatation time, that means # that the data is still valid and the captcha can be checked - seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]) - valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) - result = - with false <- is_nil(answer_data), - {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), - %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do - try do - if DateTime.before?(at, valid_if_after), - do: throw({:error, dgettext("errors", "CAPTCHA expired")}) + valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid()) - if not is_nil(Cachex.get!(:used_captcha_cache, token)), - do: throw({:error, dgettext("errors", "CAPTCHA already used")}) + if DateTime.before?(created_at, valid_if_after) do + {:error, :expired} + else + :ok + end + end - res = method().validate(token, captcha, answer_md5) - # Throw if an error occurs - if res != :ok, do: throw(res) + defp validate_usage(token) do + if is_nil(Cachex.get!(:used_captcha_cache, token)) do + :ok + else + {:error, :already_used} + end + end - # Mark this captcha as used - {:ok, _} = - Cachex.put(:used_captcha_cache, token, true, ttl: :timer.seconds(seconds_valid)) - - :ok - catch - :throw, e -> e - end - else - _ -> {:error, dgettext("errors", "Invalid answer data")} - end - - {:reply, result, state} + defp mark_captcha_as_used(token) do + ttl = seconds_valid() |> :timer.seconds() + Cachex.put(:used_captcha_cache, token, true, ttl: ttl) end defp method, do: Pleroma.Config.get!([__MODULE__, :method]) diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 06ceb20b6..6bc2fa158 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Kocaptcha do - import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -13,7 +12,7 @@ def new do case Tesla.get(endpoint <> "/new") do {:error, _} -> - %{error: dgettext("errors", "Kocaptcha service unavailable")} + %{error: :kocaptcha_service_unavailable} {:ok, res} -> json_resp = Jason.decode!(res.body) @@ -33,6 +32,6 @@ def validate(_token, captcha, answer_data) do if not is_nil(captcha) and :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data), do: :ok, - else: {:error, dgettext("errors", "Invalid CAPTCHA")} + else: {:error, :invalid} end end diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex index 06c479ca9..a90631d61 100644 --- a/lib/pleroma/captcha/native.ex +++ b/lib/pleroma/captcha/native.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Native do - import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -11,7 +10,7 @@ defmodule Pleroma.Captcha.Native do def new do case Captcha.get() do :error -> - %{error: dgettext("errors", "Captcha error")} + %{error: :captcha_error} {:ok, answer_data, img_binary} -> %{ @@ -25,7 +24,7 @@ def new do @impl Service def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok - def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")} + def validate(_token, _captcha, _answer), do: {:error, :invalid} defp token do 10 diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 4ba39b53f..3a9eec5ea 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -20,4 +20,9 @@ defmodule Pleroma.Constants do "deleted_activity_id" ] ) + + const(static_only_files, + do: + ~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) + ) end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 75c555021..7fd1b2ff6 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -311,17 +311,8 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act end end - def create_notifications(%Activity{data: %{"type" => "Follow"}} = activity) do - if Pleroma.Config.get([:notifications, :enable_follow_request_notifications]) || - Activity.follow_accepted?(activity) do - do_create_notifications(activity) - else - {:ok, []} - end - end - def create_notifications(%Activity{data: %{"type" => type}} = activity) - when type in ["Like", "Announce", "Move", "EmojiReact"] do + when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do do_create_notifications(activity) end diff --git a/lib/pleroma/plugs/instance_static.ex b/lib/pleroma/plugs/instance_static.ex index 927fa2663..7516f75c3 100644 --- a/lib/pleroma/plugs/instance_static.ex +++ b/lib/pleroma/plugs/instance_static.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Plugs.InstanceStatic do + require Pleroma.Constants + @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration. @@ -21,9 +23,6 @@ def file_path(path) do end end - @only ~w(index.html robots.txt static emoji packs sounds images instance favicon.png sw.js - sw-pleroma.js) - def init(opts) do opts |> Keyword.put(:from, "__unconfigured_instance_static_plug") @@ -31,7 +30,7 @@ def init(opts) do |> Plug.Static.init() end - for only <- @only do + for only <- Pleroma.Constants.static_only_files() do at = Plug.Router.Utils.split("/") def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex index 84b7c5d83..f44d4dee5 100644 --- a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex @@ -13,8 +13,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do def init(options), do: options defp key_id_from_conn(conn) do - with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do - Signature.key_id_to_actor_id(key_id) + with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), + {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do + ap_id else _ -> nil diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 6b0b2c969..d01728361 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Signature do alias Pleroma.Keys alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.ObjectValidators.Types def key_id_to_actor_id(key_id) do uri = @@ -21,12 +22,23 @@ def key_id_to_actor_id(key_id) do uri end - URI.to_string(uri) + maybe_ap_id = URI.to_string(uri) + + case Types.ObjectID.cast(maybe_ap_id) do + {:ok, ap_id} -> + {:ok, ap_id} + + _ -> + case Pleroma.Web.WebFinger.finger(maybe_ap_id) do + %{"ap_id" => ap_id} -> {:ok, ap_id} + _ -> {:error, maybe_ap_id} + end + end end def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - actor_id <- key_id_to_actor_id(kid), + {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} else @@ -37,7 +49,7 @@ def fetch_public_key(conn) do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - actor_id <- key_id_to_actor_id(kid), + {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b451202b2..99358ddaf 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1445,8 +1445,15 @@ def perform(:delete, %User{} = user) do end) delete_user_activities(user) - invalidate_cache(user) - Repo.delete(user) + + if user.local do + user + |> change(%{deactivated: true, email: nil}) + |> update_and_set_cache() + else + invalidate_cache(user) + Repo.delete(user) + end end def perform(:deactivate_async, user, status), do: deactivate(user, status) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index d625530ec..f607931ab 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -37,9 +37,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do [unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions ) + # Note: :following and :followers must be served even without authentication (as via :api) plug( EnsureAuthenticatedPlug - when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers] + when action in [:read_inbox, :update_outbox, :whoami, :upload_media] ) plug( diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 09119137b..c966ec960 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity + alias Pleroma.EarmarkRenderer alias Pleroma.FollowingRelationship alias Pleroma.Object alias Pleroma.Object.Containment @@ -43,6 +44,7 @@ def fix_object(object, options \\ []) do |> fix_addressing |> fix_summary |> fix_type(options) + |> fix_content end def fix_summary(%{"summary" => nil} = object) do @@ -357,6 +359,18 @@ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) def fix_type(object, _), do: object + defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object) + when is_binary(content) do + html_content = + content + |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) + |> Pleroma.HTML.filter_tags() + + Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"}) + end + + defp fix_content(object), do: object + defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do with true <- id =~ "follows", %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id), @@ -1207,18 +1221,24 @@ def add_attributed_to(object) do def prepare_attachments(object) do attachments = - (object["attachment"] || []) + object + |> Map.get("attachment", []) |> Enum.map(fn data -> [%{"mediaType" => media_type, "href" => href} | _] = data["url"] - %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"} + + %{ + "url" => href, + "mediaType" => media_type, + "name" => data["name"], + "type" => "Document" + } end) Map.put(object, "attachment", attachments) end def strip_internal_fields(object) do - object - |> Map.drop(Pleroma.Constants.object_internal_fields()) + Map.drop(object, Pleroma.Constants.object_internal_fields()) end defp strip_internal_tags(%{"tag" => tags} = object) do diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index ce40fb9e8..183df43ee 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -41,9 +41,17 @@ def pagination_params do Operation.parameter( :limit, :query, - %Schema{type: :integer, default: 20, maximum: 40}, - "Limit" + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" ) ] end + + def empty_object_response do + Operation.response("Empty object", "application/json", %Schema{type: :object, example: %{}}) + end + + def empty_array_response do + Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) + 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 d3e8bd484..fe9548b1b 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -344,7 +344,7 @@ def endorsements_operation do description: "Not implemented", security: [%{"oAuth" => ["read:accounts"]}], responses: %{ - 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + 200 => empty_array_response() } } end @@ -356,7 +356,7 @@ def identity_proofs_operation do operationId: "AccountController.identity_proofs", description: "Not implemented", responses: %{ - 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + 200 => empty_array_response() } } end diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex index 3b7f51ceb..049bcf931 100644 --- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Helpers + import Pleroma.Web.ApiSpec.Helpers def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -46,9 +46,7 @@ def create_operation do operationId: "DomainBlockController.create", requestBody: domain_block_request(), security: [%{"oAuth" => ["follow", "write:blocks"]}], - responses: %{ - 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) - } + responses: %{200 => empty_object_response()} } end @@ -67,7 +65,7 @@ def delete_operation do end defp domain_block_request do - Helpers.request_body( + request_body( "Parameters", %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex new file mode 100644 index 000000000..64adc5319 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -0,0 +1,211 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.NotificationOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.Status + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Notifications"], + summary: "Get all notifications", + description: + "Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.", + operationId: "NotificationController.index", + security: [%{"oAuth" => ["read:notifications"]}], + parameters: + [ + Operation.parameter( + :exclude_types, + :query, + %Schema{type: :array, items: notification_type()}, + "Array of types to exclude" + ), + Operation.parameter( + :account_id, + :query, + %Schema{type: :string}, + "Return only notifications received from this account" + ), + Operation.parameter( + :exclude_visibilities, + :query, + %Schema{type: :array, items: VisibilityScope}, + "Exclude the notifications for activities with the given visibilities" + ), + Operation.parameter( + :include_types, + :query, + %Schema{type: :array, items: notification_type()}, + "Include the notifications for activities with the given types" + ), + Operation.parameter( + :with_muted, + :query, + BooleanLike, + "Include the notifications from muted users" + ) + ] ++ pagination_params(), + responses: %{ + 200 => + Operation.response("Array of notifications", "application/json", %Schema{ + type: :array, + items: notification() + }), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def show_operation do + %Operation{ + tags: ["Notifications"], + summary: "Get a single notification", + description: "View information about a notification with a given ID.", + operationId: "NotificationController.show", + security: [%{"oAuth" => ["read:notifications"]}], + parameters: [id_param()], + responses: %{ + 200 => Operation.response("Notification", "application/json", notification()) + } + } + end + + def clear_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss all notifications", + description: "Clear all notifications from the server.", + operationId: "NotificationController.clear", + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def dismiss_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss a single notification", + description: "Clear a single notification from the server.", + operationId: "NotificationController.dismiss", + parameters: [id_param()], + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def dismiss_via_body_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss a single notification", + deprecated: true, + description: "Clear a single notification from the server.", + operationId: "NotificationController.dismiss_via_body", + requestBody: + request_body( + "Parameters", + %Schema{type: :object, properties: %{id: %Schema{type: :string}}}, + required: true + ), + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def destroy_multiple_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss multiple notifications", + operationId: "NotificationController.destroy_multiple", + security: [%{"oAuth" => ["write:notifications"]}], + parameters: [ + Operation.parameter( + :ids, + :query, + %Schema{type: :array, items: %Schema{type: :string}}, + "Array of notification IDs to dismiss", + required: true + ) + ], + responses: %{200 => empty_object_response()} + } + end + + defp notification do + %Schema{ + title: "Notification", + description: "Response schema for a notification", + type: :object, + properties: %{ + id: %Schema{type: :string}, + type: notification_type(), + created_at: %Schema{type: :string, format: :"date-time"}, + account: %Schema{ + allOf: [Account], + description: "The account that performed the action that generated the notification." + }, + status: %Schema{ + allOf: [Status], + description: + "Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.", + nullable: true + } + }, + example: %{ + "id" => "34975861", + "type" => "mention", + "created_at" => "2019-11-23T07:49:02.064Z", + "account" => Account.schema().example, + "status" => Status.schema().example + } + } + end + + defp notification_type do + %Schema{ + type: :string, + enum: [ + "follow", + "favourite", + "reblog", + "mention", + "poll", + "pleroma:emoji_reaction", + "move", + "follow_request" + ], + description: """ + The type of event that resulted in the notification. + + - `follow` - Someone followed you + - `mention` - Someone mentioned you in their status + - `reblog` - Someone boosted one of your statuses + - `favourite` - Someone favourited one of your statuses + - `poll` - A poll you have voted in or created has ended + - `move` - Someone moved their account + - `pleroma:emoji_reaction` - Someone reacted with emoji to your status + """ + } + end + + defp id_param do + Operation.parameter(:id, :path, :string, "Notification ID", + example: "123", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex new file mode 100644 index 000000000..da4d50703 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ReportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["reports"], + summary: "File a report", + description: "Report problematic users to your moderators", + operationId: "ReportController.create", + security: [%{"oAuth" => ["follow", "write:reports"]}], + requestBody: Helpers.request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Report", "application/json", create_response()), + 400 => Operation.response("Report", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "ReportCreateRequest", + description: "POST body for creating a report", + type: :object, + properties: %{ + account_id: %Schema{type: :string, description: "ID of the account to report"}, + status_ids: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Array of Statuses to attach to the report, for context" + }, + comment: %Schema{ + type: :string, + description: "Reason for the report" + }, + forward: %Schema{ + type: :boolean, + default: false, + description: + "If the account is remote, should the report be forwarded to the remote admin?" + } + }, + required: [:account_id], + example: %{ + "account_id" => "123", + "status_ids" => ["1337"], + "comment" => "bad status!", + "forward" => "false" + } + } + end + + defp create_response do + %Schema{ + title: "ReportResponse", + type: :object, + properties: %{ + id: %Schema{type: :string, description: "Report ID"}, + action_taken: %Schema{type: :boolean, description: "Is action taken?"} + }, + example: %{ + "id" => "123", + "action_taken" => false + } + } + end +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 4112e441a..f9db97d24 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -43,8 +43,8 @@ def unfollow(follower, unfollowed) do end def accept_follow_request(follower, followed) do - with {:ok, follower} <- User.follow(follower, followed), - %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + {:ok, follower} <- User.follow(follower, followed), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept), {:ok, _activity} <- @@ -382,9 +382,9 @@ def thread_muted?(user, activity) do ThreadMute.exists?(user.id, activity.data["context"]) end - def report(user, %{"account_id" => account_id} = data) do - with {:ok, account} <- get_reported_account(account_id), - {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]), + def report(user, data) do + with {:ok, account} <- get_reported_account(data.account_id), + {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]), {:ok, statuses} <- get_report_statuses(account, data) do ActivityPub.flag(%{ context: Utils.generate_context_id(), @@ -392,13 +392,11 @@ def report(user, %{"account_id" => account_id} = data) do account: account, statuses: statuses, content: content_html, - forward: data["forward"] || false + forward: Map.get(data, :forward, false) }) end end - def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")} - defp get_reported_account(account_id) do case User.get_cached_by_id(account_id) do %User{} = account -> {:ok, account} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 945e63e22..6540fa5d1 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -504,7 +504,8 @@ def make_report_content_html(comment) do end end - def get_report_statuses(%User{ap_id: actor}, %{"status_ids" => status_ids}) do + def get_report_statuses(%User{ap_id: actor}, %{status_ids: status_ids}) + when is_list(status_ids) do {:ok, Activity.all_by_actor_and_id(actor, status_ids)} end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 72cb3ee27..226d42c2c 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -5,6 +5,8 @@ defmodule Pleroma.Web.Endpoint do use Phoenix.Endpoint, otp_app: :pleroma + require Pleroma.Constants + socket("/socket", Pleroma.Web.UserSocket) plug(Pleroma.Plugs.SetLocalePlug) @@ -34,8 +36,7 @@ defmodule Pleroma.Web.Endpoint do Plug.Static, at: "/", from: :pleroma, - only: - ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc), + only: Pleroma.Constants.static_only_files(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, cache_control_for_etags: @static_cache_control, diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 1eedf02d6..61b0e2f63 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -94,24 +94,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts" def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do - params = - params - |> Map.take([ - :email, - :bio, - :captcha_solution, - :captcha_token, - :captcha_answer_data, - :token, - :password, - :fullname - ]) - |> Map.put(:nickname, params.username) - |> Map.put(:fullname, Map.get(params, :fullname, params.username)) - |> Map.put(:confirm, params.password) - |> Map.put(:trusted_app, app.trusted) - 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 json(conn, %{ @@ -121,7 +105,7 @@ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do created_at: Token.Utils.format_created_at(token) }) else - {:error, errors} -> json_response(conn, :bad_request, errors) + {:error, error} -> json_response(conn, :bad_request, %{error: error}) end end @@ -133,11 +117,11 @@ def create(conn, _) do render_error(conn, :forbidden, "Invalid credentials") end - defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok + defp validate_email_param(%{email: email}) when not is_nil(email), do: :ok defp validate_email_param(_) do case Pleroma.Config.get([:instance, :account_activation_required]) do - true -> {:error, %{"error" => "Missing parameters"}} + true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")} _ -> :ok end end diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 311405277..a14c86893 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,6 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do @oauth_read_actions [:show, :index] + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug( OAuthScopesPlug, %{scopes: ["read:notifications"]} when action in @oauth_read_actions @@ -20,14 +22,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation + # GET /api/v1/notifications - def index(conn, %{"account_id" => account_id} = params) do + def index(conn, %{account_id: account_id} = params) do case Pleroma.User.get_cached_by_id(account_id) do %{ap_id: account_ap_id} -> params = params - |> Map.delete("account_id") - |> Map.put("account_ap_id", account_ap_id) + |> Map.delete(:account_id) + |> Map.put(:account_ap_id, account_ap_id) index(conn, params) @@ -39,6 +43,7 @@ def index(conn, %{"account_id" => account_id} = params) do end def index(%{assigns: %{user: user}} = conn, params) do + params = Map.new(params, fn {k, v} -> {to_string(k), v} end) notifications = MastodonAPI.get_notifications(user, params) conn @@ -51,7 +56,7 @@ def index(%{assigns: %{user: user}} = conn, params) do end # GET /api/v1/notifications/:id - def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do with {:ok, notification} <- Notification.get(user, id) do render(conn, "show.json", notification: notification, for: user) else @@ -69,8 +74,8 @@ def clear(%{assigns: %{user: user}} = conn, _params) do end # POST /api/v1/notifications/:id/dismiss - # POST /api/v1/notifications/dismiss (deprecated) - def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + + def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do with {:ok, _notif} <- Notification.dismiss(user, id) do json(conn, %{}) else @@ -81,8 +86,13 @@ def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do end end + # POST /api/v1/notifications/dismiss (deprecated) + def dismiss_via_body(%{body_params: params} = conn, _) do + dismiss(conn, params) + end + # DELETE /api/v1/notifications/destroy_multiple - def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do + def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do Notification.destroy_multiple(user, ids) json(conn, %{}) end diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index 9fbaa7bd1..f65c5c62b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -9,10 +9,13 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation + @doc "POST /api/v1/reports" - def create(%{assigns: %{user: user}} = conn, params) do + def create(%{assigns: %{user: user}, body_params: params} = conn, _) do with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do render(conn, "show.json", activity: activity) end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 67214dbea..a329ffc28 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -5,10 +5,13 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do use Pleroma.Web, :view + alias Pleroma.Config + alias Pleroma.Web.ActivityPub.MRF + @mastodon_api_level "2.7.2" def render("show.json", _) do - instance = Pleroma.Config.get(:instance) + instance = Config.get(:instance) %{ uri: Pleroma.Web.base_url(), @@ -29,7 +32,58 @@ def render("show.json", _) do upload_limit: Keyword.get(instance, :upload_limit), avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit), background_upload_limit: Keyword.get(instance, :background_upload_limit), - banner_upload_limit: Keyword.get(instance, :banner_upload_limit) + banner_upload_limit: Keyword.get(instance, :banner_upload_limit), + pleroma: %{ + metadata: %{ + features: features(), + federation: federation() + }, + vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) + } } end + + def features do + [ + "pleroma_api", + "mastodon_api", + "mastodon_api_streaming", + "polls", + "pleroma_explicit_addressing", + "shareable_emoji_packs", + "multifetch", + "pleroma:api/v1/notifications:include_types_filter", + if Config.get([:media_proxy, :enabled]) do + "media_proxy" + end, + if Config.get([:gopher, :enabled]) do + "gopher" + end, + if Config.get([:chat, :enabled]) do + "chat" + end, + if Config.get([:instance, :allow_relay]) do + "relay" + end, + if Config.get([:instance, :safe_dm_mentions]) do + "safe_dm_mentions" + end, + "pleroma_emoji_reactions" + ] + |> Enum.filter(& &1) + end + + def federation do + quarantined = Config.get([:instance, :quarantined_instances], []) + + if Config.get([:instance, :mrf_transparency]) do + {:ok, data} = MRF.describe() + + data + |> Map.merge(%{quarantined_instances: quarantined}) + else + %{} + end + |> Map.put(:enabled, Config.get([:instance, :federating])) + end end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index f9a5ddcc0..721b599d4 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -9,8 +9,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web - alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.Federator.Publisher + alias Pleroma.Web.MastodonAPI.InstanceView def schemas(conn, _params) do response = %{ @@ -34,51 +34,12 @@ def schemas(conn, _params) do def raw_nodeinfo do stats = Stats.get_stats() - quarantined = Config.get([:instance, :quarantined_instances], []) - staff_accounts = User.all_superusers() |> Enum.map(fn u -> u.ap_id end) - federation_response = - if Config.get([:instance, :mrf_transparency]) do - {:ok, data} = MRF.describe() - - data - |> Map.merge(%{quarantined_instances: quarantined}) - else - %{} - end - |> Map.put(:enabled, Config.get([:instance, :federating])) - - features = - [ - "pleroma_api", - "mastodon_api", - "mastodon_api_streaming", - "polls", - "pleroma_explicit_addressing", - "shareable_emoji_packs", - "multifetch", - "pleroma:api/v1/notifications:include_types_filter", - if Config.get([:media_proxy, :enabled]) do - "media_proxy" - end, - if Config.get([:gopher, :enabled]) do - "gopher" - end, - if Config.get([:chat, :enabled]) do - "chat" - end, - if Config.get([:instance, :allow_relay]) do - "relay" - end, - if Config.get([:instance, :safe_dm_mentions]) do - "safe_dm_mentions" - end, - "pleroma_emoji_reactions" - ] - |> Enum.filter(& &1) + features = InstanceView.features() + federation = InstanceView.federation() %{ version: "2.0", @@ -106,7 +67,7 @@ def raw_nodeinfo do enabled: false }, staffAccounts: staff_accounts, - federation: federation_response, + federation: federation, pollLimits: Config.get([:instance, :poll_limits]), postFormats: Config.get([:instance, :allowed_post_formats]), uploadLimits: %{ diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index 2c1874051..1bdb3aa4d 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -61,7 +61,10 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} else users = Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1) - |> Enum.filter(& &1) + |> Enum.filter(fn + %{deactivated: false} -> true + _ -> false + end) %{ name: emoji, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a7e1f2f57..5b00243e9 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -396,7 +396,7 @@ defmodule Pleroma.Web.Router do post("/notifications/clear", NotificationController, :clear) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead - post("/notifications/dismiss", NotificationController, :dismiss) + post("/notifications/dismiss", NotificationController, :dismiss_via_body) post("/polls/:id/votes", PollController, :vote) @@ -585,6 +585,7 @@ defmodule Pleroma.Web.Router do post("/users/:nickname/outbox", ActivityPubController, :update_outbox) post("/api/ap/upload_media", ActivityPubController, :upload_media) + # The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`: get("/users/:nickname/followers", ActivityPubController, :followers) get("/users/:nickname/following", ActivityPubController, :following) end diff --git a/lib/pleroma/web/templates/layout/static_fe.html.eex b/lib/pleroma/web/templates/layout/static_fe.html.eex index 819632cec..dc0ee2a5c 100644 --- a/lib/pleroma/web/templates/layout/static_fe.html.eex +++ b/lib/pleroma/web/templates/layout/static_fe.html.eex @@ -5,7 +5,7 @@ <%= Pleroma.Config.get([:instance, :name]) %> <%= Phoenix.HTML.raw(assigns[:meta] || "") %> - +
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index cf1d9c74c..5cfb385ac 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,54 +3,27 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do + import Pleroma.Web.Gettext + alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserInviteToken - require Pleroma.Constants - def register_user(params, opts \\ []) do params = params - |> Map.take([ - :nickname, - :password, - :captcha_solution, - :captcha_token, - :captcha_answer_data, - :token, - :email, - :trusted_app - ]) - |> Map.put(:bio, User.parse_bio(params[:bio] || "")) - |> Map.put(:name, params.fullname) - |> Map.put(:password_confirmation, params[:confirm]) + |> Map.take([:email, :token, :password]) + |> Map.put(:bio, params |> Map.get(:bio, "") |> User.parse_bio()) + |> Map.put(:nickname, params[:username]) + |> Map.put(:name, Map.get(params, :fullname, params[:username])) + |> Map.put(:password_confirmation, params[:password]) - case validate_captcha(params) do - :ok -> - if Pleroma.Config.get([:instance, :registrations_open]) do - create_user(params, opts) - else - create_user_with_invite(params, opts) - end - - {:error, error} -> - # I have no idea how this error handling works - {:error, %{error: Jason.encode!(%{captcha: [error]})}} - end - end - - defp validate_captcha(params) do - if params[:trusted_app] || not Pleroma.Config.get([Pleroma.Captcha, :enabled]) do - :ok + if Pleroma.Config.get([:instance, :registrations_open]) do + create_user(params, opts) else - Pleroma.Captcha.validate( - params.captcha_token, - params.captcha_solution, - params.captcha_answer_data - ) + create_user_with_invite(params, opts) end end @@ -75,16 +48,17 @@ defp create_user(params, opts) do {:error, changeset} -> errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + changeset + |> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end) |> Jason.encode!() - {:error, %{error: errors}} + {:error, errors} end end def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), - %User{local: true, email: email} = user when not is_nil(email) <- + %User{local: true, email: email} = user when is_binary(email) <- User.get_by_nickname_or_email(nickname_or_email), {:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do user @@ -106,4 +80,58 @@ def password_reset(nickname_or_email) do {:error, "unknown user"} end end + + def validate_captcha(app, params) do + if app.trusted || not Pleroma.Captcha.enabled?() do + :ok + else + do_validate_captcha(params) + end + end + + defp do_validate_captcha(params) do + with :ok <- validate_captcha_presence(params), + :ok <- + Pleroma.Captcha.validate( + params[:captcha_token], + params[:captcha_solution], + params[:captcha_answer_data] + ) do + :ok + else + {:error, :captcha_error} -> + captcha_error(dgettext("errors", "CAPTCHA Error")) + + {:error, :invalid} -> + captcha_error(dgettext("errors", "Invalid CAPTCHA")) + + {:error, :kocaptcha_service_unavailable} -> + captcha_error(dgettext("errors", "Kocaptcha service unavailable")) + + {:error, :expired} -> + captcha_error(dgettext("errors", "CAPTCHA expired")) + + {:error, :already_used} -> + captcha_error(dgettext("errors", "CAPTCHA already used")) + + {:error, :invalid_answer_data} -> + captcha_error(dgettext("errors", "Invalid answer data")) + + {:error, error} -> + captcha_error(error) + end + end + + defp validate_captcha_presence(params) do + [:captcha_solution, :captcha_token, :captcha_answer_data] + |> Enum.find_value(:ok, fn key -> + unless is_binary(params[key]) do + error = dgettext("errors", "Invalid CAPTCHA (Missing parameter: %{name})", name: key) + {:error, error} + end + end) + end + + # For some reason FE expects error message to be a serialized JSON + defp captcha_error(error), do: {:error, Jason.encode!(%{captcha: [error]})} end diff --git a/priv/repo/migrations/20200428221338_insert_skeletons_for_deleted_users.exs b/priv/repo/migrations/20200428221338_insert_skeletons_for_deleted_users.exs new file mode 100644 index 000000000..11d9a70ba --- /dev/null +++ b/priv/repo/migrations/20200428221338_insert_skeletons_for_deleted_users.exs @@ -0,0 +1,45 @@ +defmodule Pleroma.Repo.Migrations.InsertSkeletonsForDeletedUsers do + use Ecto.Migration + + alias Pleroma.User + alias Pleroma.Repo + + import Ecto.Query + + def change do + Application.ensure_all_started(:flake_id) + + local_ap_id = + User.Query.build(%{local: true}) + |> select([u], u.ap_id) + |> limit(1) + |> Repo.one() + + unless local_ap_id == nil do + # Hack to get instance base url because getting it from Phoenix + # would require starting the whole application + instance_uri = + local_ap_id + |> URI.parse() + |> Map.put(:query, nil) + |> Map.put(:path, nil) + |> URI.to_string() + + {:ok, %{rows: ap_ids}} = + Ecto.Adapters.SQL.query( + Repo, + "select distinct unnest(nonexistent_locals.recipients) from activities, lateral (select array_agg(recipient) as recipients from unnest(activities.recipients) as recipient where recipient similar to '#{ + instance_uri + }/users/[A-Za-z0-9]*' and not(recipient in (select ap_id from users where local = true))) nonexistent_locals;", + [], + timeout: :infinity + ) + + ap_ids + |> Enum.each(fn [ap_id] -> + Ecto.Changeset.change(%User{}, deactivated: true, ap_id: ap_id) + |> Repo.insert() + end) + end + end +end diff --git a/priv/static/index.html b/priv/static/index.html index 66c9b53de..4fac5c100 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/static-fe.css b/priv/static/static-fe/static-fe.css similarity index 100% rename from priv/static/static/static-fe.css rename to priv/static/static-fe/static-fe.css diff --git a/priv/static/static/css/app.1055039ce3f2fe4dd110.css b/priv/static/static/css/app.1055039ce3f2fe4dd110.css deleted file mode 100644 index 1867ca81a..000000000 Binary files a/priv/static/static/css/app.1055039ce3f2fe4dd110.css and /dev/null differ diff --git a/priv/static/static/css/app.1055039ce3f2fe4dd110.css.map b/priv/static/static/css/app.1055039ce3f2fe4dd110.css.map deleted file mode 100644 index 861ee8313..000000000 --- a/priv/static/static/css/app.1055039ce3f2fe4dd110.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACxFA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.1055039ce3f2fe4dd110.css","sourcesContent":[".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}",".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n overflow-y: auto;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher .tabs .tab-wrapper {\n height: 28px;\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tabs .tab-wrapper .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: -93px;\n white-space: nowrap;\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tabs .tab-wrapper .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tabs .tab-wrapper .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}",".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.613cef07981cd95ccceb.css b/priv/static/static/css/app.613cef07981cd95ccceb.css new file mode 100644 index 000000000..c1d5f8188 Binary files /dev/null and b/priv/static/static/css/app.613cef07981cd95ccceb.css differ diff --git a/priv/static/static/css/app.613cef07981cd95ccceb.css.map b/priv/static/static/css/app.613cef07981cd95ccceb.css.map new file mode 100644 index 000000000..556e0bb0b --- /dev/null +++ b/priv/static/static/css/app.613cef07981cd95ccceb.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA,uBAAuB,aAAa,kBAAkB,qBAAqB,sBAAsB,qCAAqC,8BAA8B,e;ACApK,cAAc,oBAAoB,aAAa,0BAA0B,sBAAsB,wBAAwB,kBAAkB,cAAc,eAAe,gCAAgC,aAAa,wCAAwC,0BAA0B,aAAa,gBAAgB,oBAAoB,oBAAoB,aAAa,kBAAkB,WAAW,kBAAkB,gBAAgB,gBAAgB,sBAAsB,uDAAuD,cAAc,WAAW,kBAAkB,cAAc,wBAAwB,yBAAyB,wCAAwC,iCAAiC,YAAY,kBAAkB,oBAAoB,aAAa,kBAAkB,cAAc,sCAAsC,WAAW,cAAc,kBAAkB,4BAA4B,6BAA6B,gBAAgB,oBAAoB,oBAAoB,mBAAmB,cAAc,8BAA8B,yBAAyB,qCAAqC,mDAAmD,UAAU,yDAAyD,UAAU,6CAA6C,uBAAuB,UAAU,cAAc,oCAAoC,0CAA0C,gBAAgB,mBAAmB,gBAAgB,qDAAqD,WAAW,kBAAkB,OAAO,QAAQ,SAAS,UAAU,wBAAwB,yBAAyB,wC;ACAtlD,2BAA2B,aAAa,kBAAkB,kCAAkC,e","file":"static/css/app.613cef07981cd95ccceb.css","sourcesContent":[".with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}",".tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:\"\";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:\"\";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}",".with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css b/priv/static/static/css/vendors~app.18fea621d430000acc27.css similarity index 92% rename from priv/static/static/css/vendors~app.b2603a50868c68a1c192.css rename to priv/static/static/css/vendors~app.18fea621d430000acc27.css index a2e625f5e..ef783cbb3 100644 Binary files a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css and b/priv/static/static/css/vendors~app.18fea621d430000acc27.css differ diff --git a/priv/static/static/css/vendors~app.18fea621d430000acc27.css.map b/priv/static/static/css/vendors~app.18fea621d430000acc27.css.map new file mode 100644 index 000000000..057d67d6a --- /dev/null +++ b/priv/static/static/css/vendors~app.18fea621d430000acc27.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./node_modules/cropperjs/dist/cropper.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,wCAAwC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA","file":"static/css/vendors~app.18fea621d430000acc27.css","sourcesContent":["/*!\n * Cropper.js v1.5.6\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2019-10-04T04:33:44.164Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: 0.5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline: 1px solid #39f;\n outline-color: rgba(51, 153, 255, 0.75);\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: 0.5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: 0.75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center::before,\n.cropper-center::after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center::before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center::after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: 0.1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: 0.75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: 0.75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se::before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css.map b/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css.map deleted file mode 100644 index e7013b291..000000000 --- a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///./node_modules/cropperjs/dist/cropper.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,wCAAwC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA","file":"static/css/vendors~app.b2603a50868c68a1c192.css","sourcesContent":["/*!\n * Cropper.js v1.4.3\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2018-10-24T13:07:11.429Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: .5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline-color: rgba(51, 153, 255, 0.75);\n outline: 1px solid #39f;\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: .5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: .75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center:before,\n.cropper-center:after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center:before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center:after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: .1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: .75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: .75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se:before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/fontello.1587147224637.woff b/priv/static/static/font/fontello.1587147224637.woff deleted file mode 100644 index da56c9221..000000000 Binary files a/priv/static/static/font/fontello.1587147224637.woff and /dev/null differ diff --git a/priv/static/static/font/fontello.1587147224637.woff2 b/priv/static/static/font/fontello.1587147224637.woff2 deleted file mode 100644 index 6192c0f22..000000000 Binary files a/priv/static/static/font/fontello.1587147224637.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1587147224637.eot b/priv/static/static/font/fontello.1588419330867.eot similarity index 88% rename from priv/static/static/font/fontello.1587147224637.eot rename to priv/static/static/font/fontello.1588419330867.eot index 523e14f27..7f8c61e38 100644 Binary files a/priv/static/static/font/fontello.1587147224637.eot and b/priv/static/static/font/fontello.1588419330867.eot differ diff --git a/priv/static/static/font/fontello.1587147224637.svg b/priv/static/static/font/fontello.1588419330867.svg similarity index 98% rename from priv/static/static/font/fontello.1587147224637.svg rename to priv/static/static/font/fontello.1588419330867.svg index b905a0f6c..71f81f435 100644 --- a/priv/static/static/font/fontello.1587147224637.svg +++ b/priv/static/static/font/fontello.1588419330867.svg @@ -78,6 +78,10 @@ + + + + diff --git a/priv/static/static/font/fontello.1587147224637.ttf b/priv/static/static/font/fontello.1588419330867.ttf similarity index 88% rename from priv/static/static/font/fontello.1587147224637.ttf rename to priv/static/static/font/fontello.1588419330867.ttf index ec6f7f9b4..7dc4f108b 100644 Binary files a/priv/static/static/font/fontello.1587147224637.ttf and b/priv/static/static/font/fontello.1588419330867.ttf differ diff --git a/priv/static/static/font/fontello.1588419330867.woff b/priv/static/static/font/fontello.1588419330867.woff new file mode 100644 index 000000000..2bf4cbc16 Binary files /dev/null and b/priv/static/static/font/fontello.1588419330867.woff differ diff --git a/priv/static/static/font/fontello.1588419330867.woff2 b/priv/static/static/font/fontello.1588419330867.woff2 new file mode 100644 index 000000000..a31bf3f29 Binary files /dev/null and b/priv/static/static/font/fontello.1588419330867.woff2 differ diff --git a/priv/static/static/fontello.1587147224637.css b/priv/static/static/fontello.1588419330867.css similarity index 87% rename from priv/static/static/fontello.1587147224637.css rename to priv/static/static/fontello.1588419330867.css index 48e6a5b3c..198eff184 100644 Binary files a/priv/static/static/fontello.1587147224637.css and b/priv/static/static/fontello.1588419330867.css differ diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json index 5a7086a23..5963b68b4 100755 --- a/priv/static/static/fontello.json +++ b/priv/static/static/fontello.json @@ -345,6 +345,18 @@ "css": "link", "code": 59427, "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59428, + "src": "fontawesome" + }, + { + "uid": "12f4ece88e46abd864e40b35e05b11cd", + "css": "ok", + "code": 59431, + "src": "fontawesome" } ] -} +} \ No newline at end of file diff --git a/priv/static/static/js/2.1c407059cd79fca99e19.js b/priv/static/static/js/2.1c407059cd79fca99e19.js new file mode 100644 index 000000000..14018d92a Binary files /dev/null and b/priv/static/static/js/2.1c407059cd79fca99e19.js differ diff --git a/priv/static/static/js/2.1c407059cd79fca99e19.js.map b/priv/static/static/js/2.1c407059cd79fca99e19.js.map new file mode 100644 index 000000000..cfee79ea8 Binary files /dev/null and b/priv/static/static/js/2.1c407059cd79fca99e19.js.map differ diff --git a/priv/static/static/js/2.f158cbd2b8770e467dfe.js b/priv/static/static/js/2.f158cbd2b8770e467dfe.js deleted file mode 100644 index 24f80fe7b..000000000 Binary files a/priv/static/static/js/2.f158cbd2b8770e467dfe.js and /dev/null differ diff --git a/priv/static/static/js/2.f158cbd2b8770e467dfe.js.map b/priv/static/static/js/2.f158cbd2b8770e467dfe.js.map deleted file mode 100644 index 94ca6f090..000000000 Binary files a/priv/static/static/js/2.f158cbd2b8770e467dfe.js.map and /dev/null differ diff --git a/priv/static/static/js/app.def6476e8bc9b214218b.js b/priv/static/static/js/app.def6476e8bc9b214218b.js deleted file mode 100644 index 1e6ced42d..000000000 Binary files a/priv/static/static/js/app.def6476e8bc9b214218b.js and /dev/null differ diff --git a/priv/static/static/js/app.def6476e8bc9b214218b.js.map b/priv/static/static/js/app.def6476e8bc9b214218b.js.map deleted file mode 100644 index a03cad258..000000000 Binary files a/priv/static/static/js/app.def6476e8bc9b214218b.js.map and /dev/null differ diff --git a/priv/static/static/js/app.fa89b90e606f4facd209.js b/priv/static/static/js/app.fa89b90e606f4facd209.js new file mode 100644 index 000000000..a2cbcc337 Binary files /dev/null and b/priv/static/static/js/app.fa89b90e606f4facd209.js differ diff --git a/priv/static/static/js/app.fa89b90e606f4facd209.js.map b/priv/static/static/js/app.fa89b90e606f4facd209.js.map new file mode 100644 index 000000000..5722844a9 Binary files /dev/null and b/priv/static/static/js/app.fa89b90e606f4facd209.js.map differ diff --git a/priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js b/priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js new file mode 100644 index 000000000..1d62bb0a4 Binary files /dev/null and b/priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js differ diff --git a/priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js.map b/priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js.map new file mode 100644 index 000000000..ce0c86939 Binary files /dev/null and b/priv/static/static/js/vendors~app.8aa781e6dd81307f544b.js.map differ diff --git a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js b/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js deleted file mode 100644 index 8964180cd..000000000 Binary files a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js and /dev/null differ diff --git a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js.map b/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js.map deleted file mode 100644 index fab720d23..000000000 Binary files a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js.map and /dev/null differ diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 92361720e..88244a549 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/priv/static/sw-pleroma.js.map b/priv/static/sw-pleroma.js.map index 5d9874693..c704cb951 100644 Binary files a/priv/static/sw-pleroma.js.map and b/priv/static/sw-pleroma.js.map differ diff --git a/test/captcha_test.exs b/test/captcha_test.exs index ac1d846e8..1ab9019ab 100644 --- a/test/captcha_test.exs +++ b/test/captcha_test.exs @@ -61,7 +61,7 @@ test "new and validate" do assert is_binary(answer) assert :ok = Native.validate(token, answer, answer) - assert {:error, "Invalid CAPTCHA"} == Native.validate(token, answer, answer <> "foobar") + assert {:error, :invalid} == Native.validate(token, answer, answer <> "foobar") end end @@ -78,6 +78,7 @@ test "validate" do assert is_binary(answer) assert :ok = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer) + Cachex.del(:used_captcha_cache, token) end test "doesn't validate invalid answer" do @@ -92,7 +93,7 @@ test "doesn't validate invalid answer" do assert is_binary(answer) - assert {:error, "Invalid answer data"} = + assert {:error, :invalid_answer_data} = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer <> "foobar") end @@ -108,7 +109,7 @@ test "nil answer_data" do assert is_binary(answer) - assert {:error, "Invalid answer data"} = + assert {:error, :invalid_answer_data} = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", nil) end end diff --git a/test/fixtures/tesla_mock/craigmaloney.json b/test/fixtures/tesla_mock/craigmaloney.json new file mode 100644 index 000000000..56ea9c7c3 --- /dev/null +++ b/test/fixtures/tesla_mock/craigmaloney.json @@ -0,0 +1,112 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "CacheFile": "pt:CacheFile", + "Hashtag": "as:Hashtag", + "Infohash": "pt:Infohash", + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017", + "category": "sc:category", + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "downloadEnabled": { + "@id": "pt:downloadEnabled", + "@type": "sc:Boolean" + }, + "expires": "sc:expires", + "fps": { + "@id": "pt:fps", + "@type": "sc:Number" + }, + "language": "sc:inLanguage", + "licence": "sc:license", + "originallyPublishedAt": "sc:datePublished", + "position": { + "@id": "pt:position", + "@type": "sc:Number" + }, + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "sensitive": "as:sensitive", + "size": { + "@id": "pt:size", + "@type": "sc:Number" + }, + "startTimestamp": { + "@id": "pt:startTimestamp", + "@type": "sc:Number" + }, + "state": { + "@id": "pt:state", + "@type": "sc:Number" + }, + "stopTimestamp": { + "@id": "pt:stopTimestamp", + "@type": "sc:Number" + }, + "subtitleLanguage": "sc:subtitleLanguage", + "support": { + "@id": "pt:support", + "@type": "sc:Text" + }, + "uuid": "sc:identifier", + "views": { + "@id": "pt:views", + "@type": "sc:Number" + }, + "waitTranscoding": { + "@id": "pt:waitTranscoding", + "@type": "sc:Boolean" + } + }, + { + "comments": { + "@id": "as:comments", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + } + } + ], + "endpoints": { + "sharedInbox": "https://peertube.social/inbox" + }, + "followers": "https://peertube.social/accounts/craigmaloney/followers", + "following": "https://peertube.social/accounts/craigmaloney/following", + "icon": { + "mediaType": "image/png", + "type": "Image", + "url": "https://peertube.social/lazy-static/avatars/87bd694b-95bc-4066-83f4-bddfcd2b9caa.png" + }, + "id": "https://peertube.social/accounts/craigmaloney", + "inbox": "https://peertube.social/accounts/craigmaloney/inbox", + "name": "Craig Maloney", + "outbox": "https://peertube.social/accounts/craigmaloney/outbox", + "playlists": "https://peertube.social/accounts/craigmaloney/playlists", + "preferredUsername": "craigmaloney", + "publicKey": { + "id": "https://peertube.social/accounts/craigmaloney#main-key", + "owner": "https://peertube.social/accounts/craigmaloney", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9qvGIYUW01yc8CCsrwxK\n5OXlV5s7EbNWY8tJr/p1oGuELZwAnG2XKxtdbvgcCT+YxL5uRXIdCFIIIKrzRFr/\nHfS0mOgNT9u3gu+SstCNgtatciT0RVP77yiC3b2NHq1NRRvvVhzQb4cpIWObIxqh\nb2ypDClTc7XaKtgmQCbwZlGyZMT+EKz/vustD6BlpGsglRkm7iES6s1PPGb1BU+n\nS94KhbS2DOFiLcXCVWt0QarokIIuKznp4+xP1axKyP+SkT5AHx08Nd5TYFb2C1Jl\nz0WD/1q0mAN62m7QrA3SQPUgB+wWD+S3Nzf7FwNPiP4srbBgxVEUnji/r9mQ6BXC\nrQIDAQAB\n-----END PUBLIC KEY-----" + }, + "summary": null, + "type": "Person", + "url": "https://peertube.social/accounts/craigmaloney" +} diff --git a/test/fixtures/tesla_mock/peertube-social.json b/test/fixtures/tesla_mock/peertube-social.json new file mode 100644 index 000000000..0e996ba35 --- /dev/null +++ b/test/fixtures/tesla_mock/peertube-social.json @@ -0,0 +1,234 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "CacheFile": "pt:CacheFile", + "Hashtag": "as:Hashtag", + "Infohash": "pt:Infohash", + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017", + "category": "sc:category", + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "downloadEnabled": { + "@id": "pt:downloadEnabled", + "@type": "sc:Boolean" + }, + "expires": "sc:expires", + "fps": { + "@id": "pt:fps", + "@type": "sc:Number" + }, + "language": "sc:inLanguage", + "licence": "sc:license", + "originallyPublishedAt": "sc:datePublished", + "position": { + "@id": "pt:position", + "@type": "sc:Number" + }, + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "sensitive": "as:sensitive", + "size": { + "@id": "pt:size", + "@type": "sc:Number" + }, + "startTimestamp": { + "@id": "pt:startTimestamp", + "@type": "sc:Number" + }, + "state": { + "@id": "pt:state", + "@type": "sc:Number" + }, + "stopTimestamp": { + "@id": "pt:stopTimestamp", + "@type": "sc:Number" + }, + "subtitleLanguage": "sc:subtitleLanguage", + "support": { + "@id": "pt:support", + "@type": "sc:Text" + }, + "uuid": "sc:identifier", + "views": { + "@id": "pt:views", + "@type": "sc:Number" + }, + "waitTranscoding": { + "@id": "pt:waitTranscoding", + "@type": "sc:Boolean" + } + }, + { + "comments": { + "@id": "as:comments", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + } + } + ], + "attributedTo": [ + { + "id": "https://peertube.social/accounts/craigmaloney", + "type": "Person" + }, + { + "id": "https://peertube.social/video-channels/9909c7d9-6b5b-4aae-9164-c1af7229c91c", + "type": "Group" + } + ], + "category": { + "identifier": "15", + "name": "Science & Technology" + }, + "cc": [ + "https://peertube.social/accounts/craigmaloney/followers" + ], + "comments": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/comments", + "commentsEnabled": true, + "content": "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've...", + "dislikes": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/dislikes", + "downloadEnabled": true, + "duration": "PT5151S", + "icon": { + "height": 122, + "mediaType": "image/jpeg", + "type": "Image", + "url": "https://peertube.social/static/thumbnails/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe.jpg", + "width": 223 + }, + "id": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", + "language": { + "identifier": "en", + "name": "English" + }, + "licence": { + "identifier": "1", + "name": "Attribution" + }, + "likes": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/likes", + "mediaType": "text/markdown", + "name": "Twenty Years in Jail: FreeBSD's Jails, Then and Now", + "originallyPublishedAt": "2019-08-13T00:00:00.000Z", + "published": "2020-02-12T01:06:08.054Z", + "sensitive": false, + "shares": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/announces", + "state": 1, + "subtitleLanguage": [], + "support": "Learn more at http://mug.org", + "tag": [ + { + "name": "linux", + "type": "Hashtag" + }, + { + "name": "mug.org", + "type": "Hashtag" + }, + { + "name": "open", + "type": "Hashtag" + }, + { + "name": "oss", + "type": "Hashtag" + }, + { + "name": "source", + "type": "Hashtag" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Video", + "updated": "2020-02-15T15:01:09.474Z", + "url": [ + { + "href": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", + "mediaType": "text/html", + "type": "Link" + }, + { + "fps": 30, + "height": 240, + "href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.mp4", + "mediaType": "video/mp4", + "size": 119465800, + "type": "Link" + }, + { + "height": 240, + "href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.torrent", + "mediaType": "application/x-bittorrent", + "type": "Link" + }, + { + "height": 240, + "href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.torrent&xt=urn:btih:b3365331a8543bf48d09add56d7fe4b1cbbb5659&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.mp4", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "type": "Link" + }, + { + "fps": 30, + "height": 360, + "href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.mp4", + "mediaType": "video/mp4", + "size": 143930318, + "type": "Link" + }, + { + "height": 360, + "href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.torrent", + "mediaType": "application/x-bittorrent", + "type": "Link" + }, + { + "height": 360, + "href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.torrent&xt=urn:btih:0d37b23c98cb0d89e28b5dc8f49b3c97a041e569&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.mp4", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "type": "Link" + }, + { + "fps": 30, + "height": 480, + "href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.mp4", + "mediaType": "video/mp4", + "size": 130530754, + "type": "Link" + }, + { + "height": 480, + "href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.torrent", + "mediaType": "application/x-bittorrent", + "type": "Link" + }, + { + "height": 480, + "href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.torrent&xt=urn:btih:3a13ff822ad9494165eff6167183ddaaabc1372a&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.mp4", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "type": "Link" + } + ], + "uuid": "278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", + "views": 2, + "waitTranscoding": false +} diff --git a/test/notification_test.exs b/test/notification_test.exs index a24139609..a256f08d1 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -315,9 +315,7 @@ test "it creates `follow` notification for approved Follow activity" do }) end - test "if `follow_request` notifications are enabled, " <> - "it creates `follow_request` notification for pending Follow activity" do - clear_config([:notifications, :enable_follow_request_notifications], true) + test "it creates `follow_request` notification for pending Follow activity" do user = insert(:user) followed_user = insert(:user, locked: true) @@ -336,21 +334,6 @@ test "if `follow_request` notifications are enabled, " <> assert %{type: "follow"} = NotificationView.render("show.json", render_opts) end - test "if `follow_request` notifications are disabled, " <> - "it does NOT create `follow*` notification for pending Follow activity" do - clear_config([:notifications, :enable_follow_request_notifications], false) - user = insert(:user) - followed_user = insert(:user, locked: true) - - {:ok, _, _, _activity} = CommonAPI.follow(user, followed_user) - refute FollowingRelationship.following?(user, followed_user) - assert [] = Notification.for_user(followed_user) - - # After request is accepted, no new notifications are generated: - assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user) - assert [] = Notification.for_user(followed_user) - end - test "it doesn't create a notification for follow-unfollow-follow chains" do user = insert(:user) followed_user = insert(:user, locked: false) @@ -367,7 +350,6 @@ test "it doesn't create a notification for follow-unfollow-follow chains" do end test "dismisses the notification on follow request rejection" do - clear_config([:notifications, :enable_follow_request_notifications], true) user = insert(:user, locked: true) follower = insert(:user) {:ok, _, _, _follow_activity} = CommonAPI.follow(follower, user) diff --git a/test/signature_test.exs b/test/signature_test.exs index d5a2a62c4..a7a75aa4d 100644 --- a/test/signature_test.exs +++ b/test/signature_test.exs @@ -44,7 +44,8 @@ test "it returns key" do test "it returns error when not found user" do assert capture_log(fn -> - assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error} + assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) == + {:error, :error} end) =~ "[error] Could not decode user" end @@ -64,7 +65,7 @@ test "it returns key" do test "it returns error when not found user" do assert capture_log(fn -> - {:error, _} = Signature.refetch_public_key(make_fake_conn("test-ap_id")) + {:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id")) end) =~ "[error] Could not decode user" end end @@ -100,12 +101,21 @@ test "it returns error" do describe "key_id_to_actor_id/1" do test "it properly deduces the actor id for misskey" do assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") == - "https://example.com/users/1234" + {:ok, "https://example.com/users/1234"} end test "it properly deduces the actor id for mastodon and pleroma" do assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") == - "https://example.com/users/1234" + {:ok, "https://example.com/users/1234"} + end + + test "it calls webfinger for 'acct:' accounts" do + with_mock(Pleroma.Web.WebFinger, + finger: fn _ -> %{"ap_id" => "https://gensokyo.2hu/users/raymoo"} end + ) do + assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") == + {:ok, "https://gensokyo.2hu/users/raymoo"} + end end end diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex index 6dae94edf..7b0c1d5af 100644 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@ -6,12 +6,16 @@ defmodule Pleroma.Captcha.Mock do alias Pleroma.Captcha.Service @behaviour Service + @solution "63615261b77f5354fb8c4e4986477555" + + def solution, do: @solution + @impl Service def new, do: %{ type: :mock, token: "afa1815e14e29355e6c8f6b143a39fa2", - answer_data: "63615261b77f5354fb8c4e4986477555", + answer_data: @solution, url: "https://example.org/captcha.png" } diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 20cb2b3d1..9624cb0f7 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -308,6 +308,22 @@ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" }} end + def get("https://peertube.social/accounts/craigmaloney", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/craigmaloney.json") + }} + end + + def get("https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/peertube-social.json") + }} + end + def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _, [ {"accept", "application/activity+json"} ]) do diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 8df835b56..0f6ffb2b1 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -92,7 +92,7 @@ test "user is deleted" do assert_received {:mix_shell, :info, [message]} assert message =~ " deleted" - refute User.get_by_nickname(user.nickname) + assert %{deactivated: true} = User.get_by_nickname(user.nickname) end test "no user to delete" do diff --git a/test/user_test.exs b/test/user_test.exs index 347c5be72..bff337d3e 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -1135,16 +1135,7 @@ test ".delete_user_activities deletes all create activities", %{user: user} do refute Activity.get_by_id(activity.id) end - test "it deletes deactivated user" do - {:ok, user} = insert(:user, deactivated: true) |> User.set_cache() - - {:ok, job} = User.delete(user) - {:ok, _user} = ObanHelpers.perform(job) - - refute User.get_by_id(user.id) - end - - test "it deletes a user, all follow relationships and all activities", %{user: user} do + test "it deactivates a user, all follow relationships and all activities", %{user: user} do follower = insert(:user) {:ok, follower} = User.follow(follower, user) @@ -1164,8 +1155,7 @@ test "it deletes a user, all follow relationships and all activities", %{user: u follower = User.get_cached_by_id(follower.id) refute User.following?(follower, user) - refute User.get_by_id(user.id) - assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}") + assert %{deactivated: true} = User.get_by_id(user.id) user_activities = user.ap_id diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 6b5913f95..a8f1f0e26 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1055,12 +1055,12 @@ test "it works for more than 10 users", %{conn: conn} do assert result["totalItems"] == 15 end - test "returns 403 if requester is not logged in", %{conn: conn} do + test "does not require authentication", %{conn: conn} do user = insert(:user) conn |> get("/users/#{user.nickname}/followers") - |> json_response(403) + |> json_response(200) end end @@ -1152,12 +1152,12 @@ test "it works for more than 10 users", %{conn: conn} do assert result["totalItems"] == 15 end - test "returns 403 if requester is not logged in", %{conn: conn} do + test "does not require authentication", %{conn: conn} do user = insert(:user) conn |> get("/users/#{user.nickname}/following") - |> json_response(403) + |> json_response(200) end end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6057e360a..36e1e7bd1 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -872,7 +872,8 @@ test "it fails for incoming deletes with spoofed origin" do @tag capture_log: true test "it works for incoming user deletes" do - %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + %{ap_id: ap_id} = + insert(:user, ap_id: "http://mastodon.example.org/users/admin", local: false) data = File.read!("test/fixtures/mastodon-delete-user.json") @@ -1221,6 +1222,35 @@ test "it rejects activities without a valid ID" do :error = Transmogrifier.handle_incoming(data) end + test "skip converting the content when it is nil" do + object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" + + {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id) + + result = + Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil})) + + assert result["content"] == nil + end + + test "it converts content of object to html" do + object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" + + {:ok, %{"content" => content_markdown}} = + Fetcher.fetch_and_contain_remote_object_from_id(object_id) + + {:ok, %Pleroma.Object{data: %{"content" => content}} = object} = + Fetcher.fetch_object_from_id(object_id) + + assert content_markdown == + "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..." + + assert content == + "

Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership

Twenty Years in Jail: FreeBSD’s Jails, Then and Now

Jails started as a limited virtualization system, but over the last two years they’ve…

" + + assert object.data["mediaType"] == "text/html" + end + test "it remaps video URLs as attachments if necessary" do {:ok, object} = Fetcher.fetch_object_from_id( diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index f80dbf8dd..1862a9589 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1347,9 +1347,9 @@ test "returns report by its id", %{conn: conn} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) response = @@ -1374,16 +1374,16 @@ test "returns 404 when report id is invalid", %{conn: conn} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, %{id: second_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel very offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel very offended", + status_ids: [activity.id] }) %{ @@ -1523,9 +1523,9 @@ test "returns reports", %{conn: conn} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) response = @@ -1547,15 +1547,15 @@ test "returns reports with specified state", %{conn: conn} do {:ok, %{id: first_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, %{id: second_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I don't like this user" + account_id: target_user.id, + comment: "I don't like this user" }) CommonAPI.update_report_state(second_report_id, "closed") @@ -3431,9 +3431,9 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index 5db6629f2..8cfa1dcfa 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -15,7 +15,7 @@ test "renders a report" do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id}) + {:ok, activity} = CommonAPI.report(user, %{account_id: other_user.id}) expected = %{ content: nil, @@ -48,7 +48,7 @@ test "includes reported statuses" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "toot"}) {:ok, report_activity} = - CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]}) + CommonAPI.report(user, %{account_id: other_user.id, status_ids: [activity.id]}) other_user = Pleroma.User.get_by_id(other_user.id) @@ -81,7 +81,7 @@ test "renders report's state" do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id}) + {:ok, activity} = CommonAPI.report(user, %{account_id: other_user.id}) {:ok, activity} = CommonAPI.update_report_state(activity.id, "closed") assert %{state: "closed"} = @@ -94,8 +94,8 @@ test "renders report description" do {:ok, activity} = CommonAPI.report(user, %{ - "account_id" => other_user.id, - "comment" => "posts are too good for this instance" + account_id: other_user.id, + comment: "posts are too good for this instance" }) assert %{content: "posts are too good for this instance"} = @@ -108,8 +108,8 @@ test "sanitizes report description" do {:ok, activity} = CommonAPI.report(user, %{ - "account_id" => other_user.id, - "comment" => "" + account_id: other_user.id, + comment: "" }) data = Map.put(activity.data, "content", "") @@ -125,8 +125,8 @@ test "doesn't error out when the user doesn't exists" do {:ok, activity} = CommonAPI.report(user, %{ - "account_id" => other_user.id, - "comment" => "" + account_id: other_user.id, + comment: "" }) Pleroma.User.delete(other_user) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 1758662b0..bc0c1a791 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -485,9 +485,9 @@ test "creates a report" do comment = "foobar" report_data = %{ - "account_id" => target_user.id, - "comment" => comment, - "status_ids" => [activity.id] + account_id: target_user.id, + comment: comment, + status_ids: [activity.id] } note_obj = %{ @@ -517,9 +517,9 @@ test "updates report state" do {:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, report} = CommonAPI.update_report_state(report_id, "resolved") @@ -538,9 +538,9 @@ test "does not update report state when state is unsupported" do {:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"} @@ -552,16 +552,16 @@ test "updates state of multiple reports" do {:ok, %Activity{id: first_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, %Activity{id: second_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel very offended!", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel very offended!", + status_ids: [activity.id] }) {:ok, report_ids} = @@ -697,6 +697,14 @@ test "after rejection, it sets all existing pending follow request states to 're assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject" assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending" end + + test "doesn't create a following relationship if the corresponding follow request doesn't exist" do + user = insert(:user, locked: true) + not_follower = insert(:user) + CommonAPI.accept_follow_request(not_follower, user) + + assert Pleroma.FollowingRelationship.following?(not_follower, user) == false + end end describe "vote/3" do diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index ba70ba66c..b9da7e924 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -925,7 +925,8 @@ test "returns bad_request if missing email params when :account_activation_requi |> Map.put(:remote_ip, {127, 0, 0, 5}) |> post("/api/v1/accounts", Map.delete(valid_params, :email)) - assert json_response_and_validate_schema(res, 400) == %{"error" => "Missing parameters"} + assert json_response_and_validate_schema(res, 400) == + %{"error" => "Missing parameter: email"} res = conn @@ -1093,6 +1094,91 @@ test "respects rate limit setting", %{conn: conn} do end end + describe "create account with enabled captcha" do + setup %{conn: conn} do + app_token = insert(:oauth_token, user: nil) + + conn = + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) + |> put_req_header("content-type", "multipart/form-data") + + [conn: conn] + end + + setup do: clear_config([Pleroma.Captcha, :enabled], true) + + test "creates an account and returns 200 if captcha is valid", %{conn: conn} do + %{token: token, answer_data: answer_data} = Pleroma.Captcha.new() + + params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true, + captcha_solution: Pleroma.Captcha.Mock.solution(), + captcha_token: token, + captcha_answer_data: answer_data + } + + assert %{ + "access_token" => access_token, + "created_at" => _, + "scope" => ["read"], + "token_type" => "Bearer" + } = + conn + |> post("/api/v1/accounts", params) + |> json_response_and_validate_schema(:ok) + + assert Token |> Repo.get_by(token: access_token) |> Repo.preload(:user) |> Map.get(:user) + + Cachex.del(:used_captcha_cache, token) + end + + test "returns 400 if any captcha field is not provided", %{conn: conn} do + captcha_fields = [:captcha_solution, :captcha_token, :captcha_answer_data] + + valid_params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true, + captcha_solution: "xx", + captcha_token: "xx", + captcha_answer_data: "xx" + } + + for field <- captcha_fields do + expected = %{ + "error" => "{\"captcha\":[\"Invalid CAPTCHA (Missing parameter: #{field})\"]}" + } + + assert expected == + conn + |> post("/api/v1/accounts", Map.delete(valid_params, field)) + |> json_response_and_validate_schema(:bad_request) + end + end + + test "returns an error if captcha is invalid", %{conn: conn} do + params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true, + captcha_solution: "cofe", + captcha_token: "cofe", + captcha_answer_data: "cofe" + } + + assert %{"error" => "{\"captcha\":[\"Invalid answer data\"]}"} == + conn + |> post("/api/v1/accounts", params) + |> json_response_and_validate_schema(:bad_request) + end + end + describe "GET /api/v1/accounts/:id/lists - account_lists" do test "returns lists to which the account belongs" do %{user: user, conn: conn} = oauth_access(["read:lists"]) diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index 2737dcaba..2c7fd9fd0 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -34,6 +34,10 @@ test "get instance information", %{conn: conn} do "banner_upload_limit" => _ } = result + assert result["pleroma"]["metadata"]["features"] + assert result["pleroma"]["metadata"]["federation"] + assert result["pleroma"]["vapid_public_key"] + assert email == from_config_email end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 8c815b415..db380f76a 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -25,7 +25,7 @@ test "does NOT render account/pleroma/relationship if this is disabled by defaul conn |> assign(:user, user) |> get("/api/v1/notifications") - |> json_response(200) + |> json_response_and_validate_schema(200) assert Enum.all?(response, fn n -> get_in(n, ["account", "pleroma", "relationship"]) == %{} @@ -50,7 +50,9 @@ test "list of notifications" do user.ap_id }\" rel=\"ugc\">@#{user.nickname}" - assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) + assert [%{"status" => %{"content" => response}} | _rest] = + json_response_and_validate_schema(conn, 200) + assert response == expected_response end @@ -69,7 +71,7 @@ test "getting a single notification" do user.ap_id }\" rel=\"ugc\">@#{user.nickname}" - assert %{"status" => %{"content" => response}} = json_response(conn, 200) + assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200) assert response == expected_response end @@ -84,9 +86,10 @@ test "dismissing a single notification (deprecated endpoint)" do conn = conn |> assign(:user, user) - |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) + |> put_req_header("content-type", "application/json") + |> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)}) - assert %{} = json_response(conn, 200) + assert %{} = json_response_and_validate_schema(conn, 200) end test "dismissing a single notification" do @@ -102,7 +105,7 @@ test "dismissing a single notification" do |> assign(:user, user) |> post("/api/v1/notifications/#{notification.id}/dismiss") - assert %{} = json_response(conn, 200) + assert %{} = json_response_and_validate_schema(conn, 200) end test "clearing all notifications" do @@ -115,11 +118,11 @@ test "clearing all notifications" do ret_conn = post(conn, "/api/v1/notifications/clear") - assert %{} = json_response(ret_conn, 200) + assert %{} = json_response_and_validate_schema(ret_conn, 200) ret_conn = get(conn, "/api/v1/notifications") - assert all = json_response(ret_conn, 200) + assert all = json_response_and_validate_schema(ret_conn, 200) assert all == [] end @@ -143,7 +146,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do result = conn |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result @@ -151,7 +154,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do result = conn |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result @@ -159,7 +162,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do result = conn |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result end @@ -181,36 +184,28 @@ test "filters notifications for mentions" do {:ok, private_activity} = CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"}) - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["public", "unlisted", "private"] - }) + query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == direct_activity.id - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["public", "unlisted", "direct"] - }) + query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == private_activity.id - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["public", "private", "direct"] - }) + query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == unlisted_activity.id - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["unlisted", "private", "direct"] - }) + query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == public_activity.id end @@ -237,8 +232,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=direct") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -248,8 +243,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=unlisted") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -259,8 +254,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=private") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -270,8 +265,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=public") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) refute public_activity.id in activity_ids @@ -295,8 +290,8 @@ test "filters notifications for Announce activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=unlisted") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -319,25 +314,27 @@ test "filters notifications using exclude_types" do reblog_notification_id = get_notification_id_by_activity(reblog_activity) follow_notification_id = get_notification_id_by_activity(follow_activity) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) + query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) + query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^mention_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) + query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^favorite_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) + query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) end test "filters notifications using include_types" do @@ -355,32 +352,34 @@ test "filters notifications using include_types" do reblog_notification_id = get_notification_id_by_activity(reblog_activity) follow_notification_id = get_notification_id_by_activity(follow_activity) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["follow"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=follow") - assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["mention"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=mention") - assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^mention_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["favourite"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite") - assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^favorite_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["reblog"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog") - assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) - result = conn |> get("/api/v1/notifications") |> json_response(200) + result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200) assert length(result) == 4 + query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]}) + result = conn - |> get("/api/v1/notifications", %{ - include_types: ["follow", "mention", "favourite", "reblog"] - }) - |> json_response(200) + |> get("/api/v1/notifications?" <> query) + |> json_response_and_validate_schema(200) assert length(result) == 4 end @@ -402,7 +401,7 @@ test "destroy multiple" do result = conn |> get("/api/v1/notifications") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result @@ -414,22 +413,19 @@ test "destroy multiple" do result = conn2 |> get("/api/v1/notifications") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - conn_destroy = - conn - |> delete("/api/v1/notifications/destroy_multiple", %{ - "ids" => [notification1_id, notification2_id] - }) + query = params_to_query(%{ids: [notification1_id, notification2_id]}) + conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query) - assert json_response(conn_destroy, 200) == %{} + assert json_response_and_validate_schema(conn_destroy, 200) == %{} result = conn2 |> get("/api/v1/notifications") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result end @@ -443,13 +439,13 @@ test "doesn't see notifications after muting user with notifications" do ret_conn = get(conn, "/api/v1/notifications") - assert length(json_response(ret_conn, 200)) == 1 + assert length(json_response_and_validate_schema(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2) conn = get(conn, "/api/v1/notifications") - assert json_response(conn, 200) == [] + assert json_response_and_validate_schema(conn, 200) == [] end test "see notifications after muting user without notifications" do @@ -461,13 +457,13 @@ test "see notifications after muting user without notifications" do ret_conn = get(conn, "/api/v1/notifications") - assert length(json_response(ret_conn, 200)) == 1 + assert length(json_response_and_validate_schema(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2, false) conn = get(conn, "/api/v1/notifications") - assert length(json_response(conn, 200)) == 1 + assert length(json_response_and_validate_schema(conn, 200)) == 1 end test "see notifications after muting user with notifications and with_muted parameter" do @@ -479,13 +475,13 @@ test "see notifications after muting user with notifications and with_muted para ret_conn = get(conn, "/api/v1/notifications") - assert length(json_response(ret_conn, 200)) == 1 + assert length(json_response_and_validate_schema(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2) - conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) + conn = get(conn, "/api/v1/notifications?with_muted=true") - assert length(json_response(conn, 200)) == 1 + assert length(json_response_and_validate_schema(conn, 200)) == 1 end @tag capture_log: true @@ -512,7 +508,7 @@ test "see move notifications" do conn = get(conn, "/api/v1/notifications") - assert length(json_response(conn, 200)) == 1 + assert length(json_response_and_validate_schema(conn, 200)) == 1 end describe "link headers" do @@ -538,10 +534,10 @@ test "preserves parameters in link headers" do conn = conn |> assign(:user, user) - |> get("/api/v1/notifications", %{media_only: true}) + |> get("/api/v1/notifications?limit=5") assert [link_header] = get_resp_header(conn, "link") - assert link_header =~ ~r/media_only=true/ + assert link_header =~ ~r/limit=5/ assert link_header =~ ~r/min_id=#{notification2.id}/ assert link_header =~ ~r/max_id=#{notification1.id}/ end @@ -560,14 +556,14 @@ test "account_id" do assert [%{"account" => %{"id" => ^account_id}}] = conn |> assign(:user, user) - |> get("/api/v1/notifications", %{account_id: account_id}) - |> json_response(200) + |> get("/api/v1/notifications?account_id=#{account_id}") + |> json_response_and_validate_schema(200) assert %{"error" => "Account is not found"} = conn |> assign(:user, user) - |> get("/api/v1/notifications", %{account_id: "cofe"}) - |> json_response(404) + |> get("/api/v1/notifications?account_id=cofe") + |> json_response_and_validate_schema(404) end end @@ -577,4 +573,11 @@ defp get_notification_id_by_activity(%{id: id}) do |> Map.get(:id) |> to_string() end + + defp params_to_query(%{} = params) do + Enum.map_join(params, "&", fn + {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}") + {k, v} -> k <> "=" <> v + end) + end end diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/web/mastodon_api/controllers/report_controller_test.exs index 34ec8119e..21b037237 100644 --- a/test/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/web/mastodon_api/controllers/report_controller_test.exs @@ -22,8 +22,9 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do test "submit a basic report", %{conn: conn, target_user: target_user} do assert %{"action_taken" => false, "id" => _} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"account_id" => target_user.id}) - |> json_response(200) + |> json_response_and_validate_schema(200) end test "submit a report with statuses and comment", %{ @@ -33,23 +34,25 @@ test "submit a report with statuses and comment", %{ } do assert %{"action_taken" => false, "id" => _} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{ "account_id" => target_user.id, "status_ids" => [activity.id], "comment" => "bad status!", "forward" => "false" }) - |> json_response(200) + |> json_response_and_validate_schema(200) end test "account_id is required", %{ conn: conn, activity: activity } do - assert %{"error" => "Valid `account_id` required"} = + assert %{"error" => "Missing field: account_id."} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) - |> json_response(400) + |> json_response_and_validate_schema(400) end test "comment must be up to the size specified in the config", %{ @@ -63,17 +66,21 @@ test "comment must be up to the size specified in the config", %{ assert ^error = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) - |> json_response(400) + |> json_response_and_validate_schema(400) end test "returns error when account is not exist", %{ conn: conn, activity: activity } do - conn = post(conn, "/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) - assert json_response(conn, 400) == %{"error" => "Account not found"} + assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"} end test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do @@ -81,7 +88,8 @@ test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_ assert %{"action_taken" => false, "id" => _} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"account_id" => target_user.id}) - |> json_response(200) + |> json_response_and_validate_schema(200) end end diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs index 8d0e70db8..f120bd0cd 100644 --- a/test/web/mastodon_api/controllers/suggestion_controller_test.exs +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -5,8 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Config - setup do: oauth_access(["read"]) test "returns empty result", %{conn: conn} do diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 7926a0757..368533292 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -18,7 +18,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do test "it registers a new user and returns the user." do data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :password => "bear", @@ -35,7 +35,7 @@ test "it registers a new user and returns the user." do test "it registers a new user with empty string in bio and returns the user." do data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :bio => "", @@ -60,7 +60,7 @@ test "it sends confirmation email if :account_activation_required is specified i end data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :bio => "", @@ -87,7 +87,7 @@ test "it sends confirmation email if :account_activation_required is specified i test "it registers a new user and parses mentions in the bio" do data1 = %{ - :nickname => "john", + :username => "john", :email => "john@gmail.com", :fullname => "John Doe", :bio => "test", @@ -98,7 +98,7 @@ test "it registers a new user and parses mentions in the bio" do {:ok, user1} = TwitterAPI.register_user(data1) data2 = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :bio => "@john test", @@ -123,7 +123,7 @@ test "returns user on success" do {:ok, invite} = UserInviteToken.create_invite() data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -145,7 +145,7 @@ test "returns user on success" do test "returns error on invalid token" do data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -165,7 +165,7 @@ test "returns error on expired token" do UserInviteToken.update_invite!(invite, used: true) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -186,7 +186,7 @@ test "returns error on expired token" do setup do data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -250,7 +250,7 @@ test "returns user on success, after him registration fails" do UserInviteToken.update_invite!(invite, uses: 99) data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -269,7 +269,7 @@ test "returns user on success, after him registration fails" do AccountView.render("show.json", %{user: fetched_user}) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -292,7 +292,7 @@ test "returns user on success" do {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100}) data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -317,7 +317,7 @@ test "error after max uses" do UserInviteToken.update_invite!(invite, uses: 99) data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -335,7 +335,7 @@ test "error after max uses" do AccountView.render("show.json", %{user: fetched_user}) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -355,7 +355,7 @@ test "returns error on overdue date" do UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100}) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -377,7 +377,7 @@ test "returns error on with overdue date and after max" do UserInviteToken.update_invite!(invite, uses: 100) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -395,16 +395,15 @@ test "returns error on with overdue date and after max" do test "it returns the error on registration problems" do data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", - :bio => "close the world.", - :password => "bear" + :bio => "close the world." } - {:error, error_object} = TwitterAPI.register_user(data) + {:error, error} = TwitterAPI.register_user(data) - assert is_binary(error_object[:error]) + assert is_binary(error) refute User.get_cached_by_nickname("lain") end