diff options
113 files changed, 2724 insertions, 1194 deletions
diff --git a/.gitattributes b/.gitattributes index 68895bf88..eb0c94757 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,10 @@ *.ex diff=elixir *.exs diff=elixir -# At the time of writing all js/css files included -# in the repo are minified bundles, and we don't want -# to search/diff those as text files. + +priv/static/instance/static.css diff=css + +# Most of js/css files included in the repo are minified bundles, +# and we don't want to search/diff those as text files. *.js binary *.js.map binary *.css binary diff --git a/CHANGELOG.md b/CHANGELOG.md index 72dfbedd0..c6bf38ee0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Polls now always return a `voters_count`, even if they are single-choice. - Admin Emails: The ap id is used as the user link in emails now. +- Improved registration workflow for email confirmation and account approval modes. +- **Breaking:** Changed `mix pleroma.user toggle_confirmed` to `mix pleroma.user confirm` - Search: When using Postgres 11+, Pleroma will use the `websearch_to_tsvector` function to parse search queries. - Emoji: Support the full Unicode 13.1 set of Emoji for reactions, plus regional indicators. @@ -17,14 +19,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Reports now generate notifications for admins and mods. - Experimental websocket-based federation between Pleroma instances. -- Support for local-only statuses +- Support for local-only statuses. - Support pagination of blocks and mutes. - Account backup. - Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance. - Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`. - The site title is now injected as a `title` tag like preloads or metadata. - Password reset tokens now are not accepted after a certain age. -- Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config` +- Mix tasks to help with displaying and removing ConfigDB entries. See `mix pleroma.config`. +- OAuth form improvements: users are remembered by their cookie, the CSS is overridable by the admin, and the style has been improved. +- OAuth improvements and fixes: more secure session-based authentication (by token that could be revoked anytime), ability to revoke belonging OAuth token from any client etc. <details> <summary>API Changes</summary> @@ -32,13 +36,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending. - Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances. - Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute. -- Admin API: An endpoint to manage frontends - +- Admin API: An endpoint to manage frontends. +- Streaming API: Add follow relationships updates. </details> ### Fixed - Users with `is_discoverable` field set to false (default value) will appear in in-service search results but be hidden from external services (search bots etc.). +- Streaming API: Posts and notifications are not dropped, when CLI task is executing. <details> <summary>API Changes</summary> @@ -59,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mix task pleroma.user delete_activities for source installations. - Fix ability to update Pleroma Chat push notifications with PUT /api/v1/push/subscription and alert type pleroma:chat_mention - Forwarded reports duplication from Pleroma instances. +- Rich Media Previews sometimes showed the wrong preview due to a bug following redirects. <details> <summary>API</summary> @@ -102,7 +108,7 @@ switched to a new configuration mechanism, however it was not officially removed - Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details). - Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`) -- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`) +- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email resend_confirmation_emails`) - Mix task option for force-unfollowing relays - App metrics: ability to restrict access to specified IP whitelist. diff --git a/benchmarks/load_testing/users.ex b/benchmarks/load_testing/users.ex index 6cf3958c1..34a904ac2 100644 --- a/benchmarks/load_testing/users.ex +++ b/benchmarks/load_testing/users.ex @@ -109,8 +109,8 @@ defmodule Pleroma.LoadTesting.Users do end def make_friends(%User{} = main_user, %User{} = user) do - {:ok, _} = User.follow(main_user, user) - {:ok, _} = User.follow(user, main_user) + {:ok, _, _} = User.follow(main_user, user) + {:ok, _, _} = User.follow(user, main_user) end @spec get_users(User.t(), keyword()) :: [User.t()] diff --git a/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex b/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex index 9b7ac6111..aed32f194 100644 --- a/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex +++ b/benchmarks/mix/tasks/pleroma/benchmarks/timelines.ex @@ -50,7 +50,7 @@ defmodule Mix.Tasks.Pleroma.Benchmarks.Timelines do ) users - |> Enum.each(fn {:ok, follower} -> Pleroma.User.follow(follower, user) end) + |> Enum.each(fn {:ok, follower, user} -> Pleroma.User.follow(follower, user) end) Benchee.run( %{ diff --git a/config/config.exs b/config/config.exs index f7455cf97..3ccb6a3f5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -306,7 +306,7 @@ config :pleroma, :frontend_configurations, hideSitename: false, hideUserStats: false, loginMethod: "password", - logo: "/static/logo.png", + logo: "/static/logo.svg", logoMargin: ".1em", logoMask: true, minimalScopesMode: false, @@ -343,8 +343,8 @@ config :pleroma, :assets, config :pleroma, :manifest, icons: [ %{ - src: "/static/logo.png", - type: "image/png" + src: "/static/logo.svg", + type: "image/svg+xml" } ], theme_color: "#282c37", @@ -648,7 +648,7 @@ config :pleroma, :email_notifications, } config :pleroma, :oauth2, - token_expires_in: 600, + token_expires_in: 3600 * 24 * 365 * 100, issue_new_refresh_token: true, clean_expired_tokens: false diff --git a/config/description.exs b/config/description.exs index a663d8127..a916a0711 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1254,7 +1254,7 @@ config :pleroma, :config_description, [ hideSitename: false, hideUserStats: false, loginMethod: "password", - logo: "/static/logo.png", + logo: "/static/logo.svg", logoMargin: ".1em", logoMask: true, minimalScopesMode: false, @@ -1340,7 +1340,7 @@ config :pleroma, :config_description, [ key: :logo, type: {:string, :image}, description: "URL of the logo, defaults to Pleroma's logo", - suggestions: ["/static/logo.png"] + suggestions: ["/static/logo.svg"] }, %{ key: :logoMargin, @@ -2540,7 +2540,7 @@ config :pleroma, :config_description, [ key: :token_expires_in, type: :integer, description: "The lifetime in seconds of the access token", - suggestions: [600] + suggestions: [2_592_000] }, %{ key: :issue_new_refresh_token, diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 6b0ad85d1..1b197e073 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -4,7 +4,7 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma ## Flake IDs -Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings +Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However, just like Mastodon's ids, they are lexically sortable strings ## Timelines @@ -26,8 +26,8 @@ Has these additional fields under the `pleroma` object: - `conversation_id`: the ID of the AP context the status is associated with (if any) - `direct_conversation_id`: the ID of the Mastodon direct message conversation the status is associated with (if any) - `in_reply_to_account_acct`: the `acct` property of User entity for replied user (if any) -- `content`: a map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` -- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` +- `content`: a map consisting of alternate representations of the `content` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain` +- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain` - `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire - `thread_muted`: true if the thread the post belongs to is muted - `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint. @@ -170,9 +170,9 @@ Returns on success: 200 OK `{}` Additional parameters can be added to the JSON body/Form data: -- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. +- `preview`: boolean, if set to `true` the post won't be actually posted, but the status entity would still be rendered back. This could be useful for previewing rich text/custom emoji, for example. - `content_type`: string, contain the MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint. -- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply. +- `to`: A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for post visibility are not affected by this and will still apply. - `visibility`: string, besides standard MastoAPI values (`direct`, `private`, `unlisted`, `local` or `public`) it can be used to address a List by setting it to `list:LIST_ID`. - `expires_in`: The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour. - `in_reply_to_conversation_id`: Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`. @@ -279,10 +279,27 @@ Has these additional fields under the `pleroma` object: ## Streaming +### Chats + There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field. +### Remote timelines + For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`. +### Follow relationships updates + +Pleroma streams follow relationships updates as `pleroma:follow_relationships_update` events to the `user` stream. + +The message payload consist of: + +- `state`: a relationship state, one of `follow_pending`, `follow_accept` or `follow_reject`. + +- `follower` and `following` maps with following fields: + - `id`: user ID + - `follower_count`: follower count + - `following_count`: following count + ## User muting and thread muting Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds. diff --git a/docs/administration/CLI_tasks/email.md b/docs/administration/CLI_tasks/email.md index d9aa0e71b..2bb57bea4 100644 --- a/docs/administration/CLI_tasks/email.md +++ b/docs/administration/CLI_tasks/email.md @@ -16,8 +16,7 @@ mix pleroma.email test [--to <destination email address>] ``` - -Example: +Example: === "OTP" @@ -36,11 +35,11 @@ Example: === "OTP" ```sh - ./bin/pleroma_ctl email send_confirmation_mails + ./bin/pleroma_ctl email resend_confirmation_emails ``` === "From Source" ```sh - mix pleroma.email send_confirmation_mails + mix pleroma.email resend_confirmation_emails ``` diff --git a/docs/administration/CLI_tasks/user.md b/docs/administration/CLI_tasks/user.md index c64ed4f22..b57dce0e7 100644 --- a/docs/administration/CLI_tasks/user.md +++ b/docs/administration/CLI_tasks/user.md @@ -264,13 +264,13 @@ === "OTP" ```sh - ./bin/pleroma_ctl user toggle_confirmed <nickname> + ./bin/pleroma_ctl user confirm <nickname> ``` === "From Source" ```sh - mix pleroma.user toggle_confirmed <nickname> + mix pleroma.user confirm <nickname> ``` ## Set confirmation status for all regular active users diff --git a/docs/configuration/static_dir.md b/docs/configuration/static_dir.md index 8ac07b725..a294bb604 100644 --- a/docs/configuration/static_dir.md +++ b/docs/configuration/static_dir.md @@ -88,3 +88,8 @@ config :pleroma, :frontend_configurations, Note the extra `static` folder for the terms-of-service.html Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by adding and changing `$static_dir/static/terms-of-service.html`. + + +## Styling rendered pages + +To overwrite the CSS stylesheet of the OAuth form and other static pages, you can upload your own CSS file to `instance/static/static.css`. This will completely replace the CSS used by those pages, so it might be a good idea to copy the one from `priv/static/instance/static.css` and make your changes. diff --git a/docs/dev.md b/docs/dev.md index aa89a941f..765380a58 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -14,9 +14,9 @@ This document contains notes and guidelines for Pleroma developers. For `:api` pipeline routes, it'll be verified whether `OAuthScopesPlug` was called or explicitly skipped, and if it was not then auth information will be dropped for request. Then `EnsurePublicOrAuthenticatedPlug` will be called to ensure that either the instance is not private or user is authenticated (unless explicitly skipped). Such automated checks help to prevent human errors and result in higher security / privacy for users. -## [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) +## Non-OAuth authentication -* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Web.Plugs.AuthenticationPlug` and `Pleroma.Web.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided. +* With non-OAuth authentication ([HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization) or HTTP header- or params-provided auth), OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways); auth plugs invoke `Pleroma.Helpers.AuthHelper.skip_oauth(conn)` in this case. ## Auth-related configuration, OAuth consumer mode etc. diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex index 7575f0ef8..a33a9951c 100644 --- a/lib/mix/pleroma.ex +++ b/lib/mix/pleroma.ex @@ -12,7 +12,8 @@ defmodule Mix.Pleroma do :cachex, :flake_id, :swoosh, - :timex + :timex, + :fast_html ] @cachex_children ["object", "user", "scrubber", "web_resp"] @doc "Common functions to be reused in mix tasks" @@ -37,12 +38,23 @@ defmodule Mix.Pleroma do Enum.each(apps, &Application.ensure_all_started/1) + oban_config = [ + crontab: [], + repo: Pleroma.Repo, + log: false, + queues: [], + plugins: [] + ] + children = [ Pleroma.Repo, + Pleroma.Emoji, {Pleroma.Config.TransferTask, false}, Pleroma.Web.Endpoint, - {Oban, Pleroma.Config.get(Oban)} + {Oban, oban_config}, + {Majic.Pool, + [name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]} ] ++ http_children(adapter) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index a01c36ece..22151ce08 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -48,9 +48,15 @@ defmodule Mix.Tasks.Pleroma.Database do def run(["update_users_following_followers_counts"]) do start_pleroma() - User - |> Repo.all() - |> Enum.each(&User.update_follower_count/1) + Repo.transaction( + fn -> + from(u in User, select: u) + |> Repo.stream() + |> Stream.each(&User.update_follower_count/1) + |> Stream.run() + end, + timeout: :infinity + ) end def run(["prune_objects" | args]) do diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index ac8688424..853c4eaa2 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -161,12 +161,21 @@ defmodule Mix.Tasks.Pleroma.Instance do ) |> Path.expand() + {strip_uploads_message, strip_uploads_default} = + if Pleroma.Utils.command_available?("exiftool") do + {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)", + "y"} + else + {"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)", + "n"} + end + strip_uploads = get_option( options, :strip_uploads, - "Do you want to strip location (GPS) data from uploaded images? (y/n)", - "y" + strip_uploads_message, + strip_uploads_default ) === "y" anonymize_uploads = @@ -253,7 +262,7 @@ defmodule Mix.Tasks.Pleroma.Instance do else shell_error( "The task would have overwritten the following files:\n" <> - (Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <> + (Enum.map(will_overwrite, &"- #{&1}\n") |> Enum.join("")) <> "Rerun with `--force` to overwrite them." ) end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index ca9c8579f..20fe6c6e4 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -345,11 +345,11 @@ defmodule Mix.Tasks.Pleroma.User do end end - def run(["toggle_confirmed", nickname]) do + def run(["confirm", nickname]) do start_pleroma() with %User{} = user <- User.get_cached_by_nickname(nickname) do - {:ok, user} = User.toggle_confirmation(user) + {:ok, user} = User.confirm(user) message = if user.confirmation_pending, do: "needs", else: "doesn't need" diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 806a61fd2..d3625dbf2 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -93,6 +93,19 @@ defmodule Pleroma.Emails.UserEmail do |> html_body(html_body) end + def approval_pending_email(user) do + html_body = """ + <h3>Awaiting Approval</h3> + <p>Your account at #{instance_name()} is being reviewed by staff. You will receive another email once your account is approved.</p> + """ + + new() + |> to(recipient(user)) + |> from(sender()) + |> subject("Your account is awaiting approval") + |> html_body(html_body) + end + @doc """ Email used in digest email notifications Includes Mentions and New Followers data @@ -151,7 +164,7 @@ defmodule Pleroma.Emails.UserEmail do logo_path = if is_nil(logo) do - Path.join(:code.priv_dir(:pleroma), "static/static/logo.png") + Path.join(:code.priv_dir(:pleroma), "static/static/logo.svg") else Path.join(Config.get([:instance, :static_dir]), logo) end @@ -162,7 +175,7 @@ defmodule Pleroma.Emails.UserEmail do |> subject("Your digest from #{instance_name()}") |> put_layout(false) |> render_body("digest.html", html_data) - |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.png", type: :inline)) + |> attachment(Swoosh.Attachment.new(logo_path, filename: "logo.svg", type: :inline)) end end diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index ca58e5432..f768af19f 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -22,14 +22,14 @@ defmodule Pleroma.Emoji.Pack do alias Pleroma.Emoji alias Pleroma.Emoji.Pack + alias Pleroma.Utils @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} def create(name) do with :ok <- validate_not_empty([name]), dir <- Path.join(emoji_path(), name), :ok <- File.mkdir(dir) do - %__MODULE__{pack_file: Path.join(dir, "pack.json")} - |> save_pack() + save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")}) end end @@ -62,10 +62,9 @@ defmodule Pleroma.Emoji.Pack do @spec delete(String.t()) :: {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} def delete(name) do - with :ok <- validate_not_empty([name]) do - emoji_path() - |> Path.join(name) - |> File.rm_rf() + with :ok <- validate_not_empty([name]), + pack_path <- Path.join(emoji_path(), name) do + File.rm_rf(pack_path) end end @@ -94,7 +93,7 @@ defmodule Pleroma.Emoji.Pack do def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do with {:ok, zip_files} <- :zip.table(to_charlist(file.path)), [_ | _] = emojies <- unpack_zip_emojies(zip_files), - {:ok, tmp_dir} <- Pleroma.Utils.tmp_dir("emoji") do + {:ok, tmp_dir} <- Utils.tmp_dir("emoji") do try do {:ok, _emoji_files} = :zip.unzip( @@ -282,18 +281,21 @@ defmodule Pleroma.Emoji.Pack do end end - @spec load_pack(String.t()) :: {:ok, t()} | {:error, :not_found} + @spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()} def load_pack(name) do pack_file = Path.join([emoji_path(), name, "pack.json"]) - if File.exists?(pack_file) do + with {:ok, _} <- File.stat(pack_file), + {:ok, pack_data} <- File.read(pack_file) do pack = - pack_file - |> File.read!() - |> from_json() - |> Map.put(:pack_file, pack_file) - |> Map.put(:path, Path.dirname(pack_file)) - |> Map.put(:name, name) + from_json( + pack_data, + %{ + pack_file: pack_file, + path: Path.dirname(pack_file), + name: name + } + ) files_count = pack.files @@ -301,8 +303,6 @@ defmodule Pleroma.Emoji.Pack do |> length() {:ok, Map.put(pack, :files_count, files_count)} - else - {:error, :not_found} end end @@ -434,10 +434,17 @@ defmodule Pleroma.Emoji.Pack do end end - defp from_json(json) do + defp from_json(json, attrs) do map = Jason.decode!(json) - struct(__MODULE__, %{files: map["files"], pack: map["pack"]}) + pack_attrs = + attrs + |> Map.merge(%{ + files: map["files"], + pack: map["pack"] + }) + + struct(__MODULE__, pack_attrs) end defp validate_shareable_packs_available(uri) do @@ -491,10 +498,10 @@ defmodule Pleroma.Emoji.Pack do end defp create_subdirs(file_path) do - if String.contains?(file_path, "/") do - file_path - |> Path.dirname() - |> File.mkdir_p!() + with true <- String.contains?(file_path, "/"), + path <- Path.dirname(file_path), + false <- File.exists?(path) do + File.mkdir_p!(path) end end @@ -518,10 +525,15 @@ defmodule Pleroma.Emoji.Pack do defp get_filename(pack, shortcode) do with %{^shortcode => filename} when is_binary(filename) <- pack.files, - true <- pack.path |> Path.join(filename) |> File.exists?() do + file_path <- Path.join(pack.path, filename), + {:ok, _} <- File.stat(file_path) do {:ok, filename} else - _ -> {:error, :doesnt_exist} + {:error, _} = error -> + error + + _ -> + {:error, :doesnt_exist} end end diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 2039a259d..5390a58e1 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -62,23 +62,47 @@ defmodule Pleroma.FollowingRelationship do follow(follower, following, state) following_relationship -> - following_relationship - |> cast(%{state: state}, [:state]) - |> validate_required([:state]) - |> Repo.update() + with {:ok, _following_relationship} <- + following_relationship + |> cast(%{state: state}, [:state]) + |> validate_required([:state]) + |> Repo.update() do + after_update(state, follower, following) + end end end def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do - %__MODULE__{} - |> changeset(%{follower: follower, following: following, state: state}) - |> Repo.insert(on_conflict: :nothing) + with {:ok, _following_relationship} <- + %__MODULE__{} + |> changeset(%{follower: follower, following: following, state: state}) + |> Repo.insert(on_conflict: :nothing) do + after_update(state, follower, following) + end end def unfollow(%User{} = follower, %User{} = following) do case get(follower, following) do - %__MODULE__{} = following_relationship -> Repo.delete(following_relationship) - _ -> {:ok, nil} + %__MODULE__{} = following_relationship -> + with {:ok, _following_relationship} <- Repo.delete(following_relationship) do + after_update(:unfollow, follower, following) + end + + _ -> + {:ok, nil} + end + end + + defp after_update(state, %User{} = follower, %User{} = following) do + with {:ok, following} <- User.update_follower_count(following), + {:ok, follower} <- User.update_following_count(follower) do + Pleroma.Web.Streamer.stream("follow_relationship", %{ + state: state, + following: following, + follower: follower + }) + + {:ok, follower, following} end end diff --git a/lib/pleroma/helpers/auth_helper.ex b/lib/pleroma/helpers/auth_helper.ex new file mode 100644 index 000000000..8f87b38be --- /dev/null +++ b/lib/pleroma/helpers/auth_helper.ex @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Helpers.AuthHelper do + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Plug.Conn + + import Plug.Conn + + @oauth_token_session_key :oauth_token + + @doc """ + Skips OAuth permissions (scopes) checks, assigns nil `:token`. + Intended to be used with explicit authentication and only when OAuth token cannot be determined. + """ + def skip_oauth(conn) do + conn + |> assign(:token, nil) + |> OAuthScopesPlug.skip_plug() + end + + @doc "Drops authentication info from connection" + def drop_auth_info(conn) do + # To simplify debugging, setting a private variable on `conn` if auth info is dropped + conn + |> assign(:user, nil) + |> assign(:token, nil) + |> put_private(:authentication_ignored, true) + end + + @doc "Gets OAuth token string from session" + def get_session_token(%Conn{} = conn) do + get_session(conn, @oauth_token_session_key) + end + + @doc "Updates OAuth token string in session" + def put_session_token(%Conn{} = conn, token) when is_binary(token) do + put_session(conn, @oauth_token_session_key, token) + end + + @doc "Deletes OAuth token string from session" + def delete_session_token(%Conn{} = conn) do + delete_session(conn, @oauth_token_session_key) + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 4b3a9d690..5a7704ddb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -806,18 +806,50 @@ defmodule Pleroma.User do end end - def post_register_action(%User{} = user) do + def post_register_action(%User{confirmation_pending: true} = user) do + with {:ok, _} <- try_send_confirmation_email(user) do + {:ok, user} + end + end + + def post_register_action(%User{approval_pending: true} = user) do + with {:ok, _} <- send_user_approval_email(user), + {:ok, _} <- send_admin_approval_emails(user) do + {:ok, user} + end + end + + def post_register_action(%User{approval_pending: false, confirmation_pending: false} = user) do with {:ok, user} <- autofollow_users(user), {:ok, _} <- autofollowing_users(user), {:ok, user} <- set_cache(user), {:ok, _} <- send_welcome_email(user), {:ok, _} <- send_welcome_message(user), - {:ok, _} <- send_welcome_chat_message(user), - {:ok, _} <- try_send_confirmation_email(user) do + {:ok, _} <- send_welcome_chat_message(user) do {:ok, user} end end + defp send_user_approval_email(user) do + user + |> Pleroma.Emails.UserEmail.approval_pending_email() + |> Pleroma.Emails.Mailer.deliver_async() + + {:ok, :enqueued} + end + + defp send_admin_approval_emails(user) do + all_superusers() + |> Enum.filter(fn user -> not is_nil(user.email) end) + |> Enum.each(fn superuser -> + superuser + |> Pleroma.Emails.AdminEmail.new_unapproved_registration(user) + |> Pleroma.Emails.Mailer.deliver_async() + end) + + {:ok, :enqueued} + end + def send_welcome_message(user) do if User.WelcomeMessage.enabled?() do User.WelcomeMessage.post_message(user) @@ -894,7 +926,7 @@ defmodule Pleroma.User do if not ap_enabled?(followed) do follow(follower, followed) else - {:ok, follower} + {:ok, follower, followed} end end @@ -920,11 +952,6 @@ defmodule Pleroma.User do true -> FollowingRelationship.follow(follower, followed, state) - - {:ok, _} = update_follower_count(followed) - - follower - |> update_following_count() end end @@ -948,11 +975,6 @@ defmodule Pleroma.User do case get_follow_state(follower, followed) do state when state in [:follow_pending, :follow_accept] -> FollowingRelationship.unfollow(follower, followed) - {:ok, followed} = update_follower_count(followed) - - {:ok, follower} = update_following_count(follower) - - {:ok, follower, followed} nil -> {:error, "Not subscribed!"} @@ -1600,11 +1622,34 @@ defmodule Pleroma.User do end) end - def approve(%User{} = user) do - change(user, approval_pending: false) - |> update_and_set_cache() + def approve(%User{approval_pending: true} = user) do + with chg <- change(user, approval_pending: false), + {:ok, user} <- update_and_set_cache(chg) do + post_register_action(user) + {:ok, user} + end + end + + def approve(%User{} = user), do: {:ok, user} + + def confirm(users) when is_list(users) do + Repo.transaction(fn -> + Enum.map(users, fn user -> + with {:ok, user} <- confirm(user), do: user + end) + end) end + def confirm(%User{confirmation_pending: true} = user) do + with chg <- confirmation_changeset(user, need_confirmation: false), + {:ok, user} <- update_and_set_cache(chg) do + post_register_action(user) + {:ok, user} + end + end + + def confirm(%User{} = user), do: {:ok, user} + def update_notification_settings(%User{} = user, settings) do user |> cast(%{notification_settings: settings}, []) @@ -2091,18 +2136,6 @@ defmodule Pleroma.User do updated_user end - @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()} - def toggle_confirmation(%User{} = user) do - user - |> confirmation_changeset(need_confirmation: !user.confirmation_pending) - |> update_and_set_cache() - end - - @spec toggle_confirmation([User.t()]) :: [{:ok, User.t()} | {:error, Changeset.t()}] - def toggle_confirmation(users) do - Enum.map(users, &toggle_confirmation/1) - end - @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()} def need_confirmation(%User{} = user, bool) do user @@ -2418,4 +2451,8 @@ defmodule Pleroma.User do |> Map.put(:bio, HTML.filter_tags(user.bio, filter)) |> Map.put(:fields, fields) end + + def get_host(%User{ap_id: ap_id} = _user) do + URI.parse(ap_id).host + end end diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index e458021c8..86b49d8ae 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -45,7 +45,7 @@ defmodule Pleroma.User.Import do identifiers, fn identifier -> with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), - {:ok, follower} <- User.maybe_direct_follow(follower, followed), + {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed), {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do followed else diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index e95766223..fa75a8c99 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -3,6 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Utils do + @posix_error_codes ~w( + eacces eagain ebadf ebadmsg ebusy edeadlk edeadlock edquot eexist efault + efbig eftype eintr einval eio eisdir eloop emfile emlink emultihop + enametoolong enfile enobufs enodev enolck enolink enoent enomem enospc + enosr enostr enosys enotblk enotdir enotsup enxio eopnotsupp eoverflow + eperm epipe erange erofs espipe esrch estale etxtbsy exdev + )a + def compile_dir(dir) when is_binary(dir) do dir |> File.ls!() @@ -44,4 +52,12 @@ defmodule Pleroma.Utils do error -> error end end + + @spec posix_error_message(atom()) :: binary() + def posix_error_message(code) when code in @posix_error_codes do + error_message = Gettext.dgettext(Pleroma.Web.Gettext, "posix_errors", "#{code}") + "(POSIX error: #{error_message})" + end + + def posix_error_message(_), do: "" end diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index 6ed19d3dd..3ca20455d 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -20,6 +20,7 @@ defmodule Pleroma.Web do below. """ + alias Pleroma.Helpers.AuthHelper alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug @@ -75,7 +76,7 @@ defmodule Pleroma.Web do defp maybe_drop_authentication_if_oauth_check_ignored(conn) do if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do - OAuthScopesPlug.drop_auth_info(conn) + AuthHelper.drop_auth_info(conn) else conn end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 4d8fb721e..8556fca1d 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -47,10 +47,9 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do %User{} = followed <- User.get_cached_by_ap_id(actor), %User{} = follower <- User.get_cached_by_ap_id(follower_id), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), - {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do + {:ok, _follower, followed} <- + FollowingRelationship.update(follower, followed, :follow_accept) do Notification.update_notification_type(followed, follow_activity) - User.update_follower_count(followed) - User.update_following_count(follower) end {:ok, object, meta} @@ -99,7 +98,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do ) do with %User{} = follower <- User.get_cached_by_ap_id(following_user), %User{} = followed <- User.get_cached_by_ap_id(followed_user), - {_, {:ok, _}, _, _} <- + {_, {:ok, _, _}, _, _} <- {:following, User.follow(follower, followed, :follow_pending), follower, followed} do if followed.local && !followed.is_locked do {:ok, accept_data, _} = Builder.accept(followed, object) diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 5c2c282b3..75525104f 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -415,7 +415,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do users = Enum.map(nicknames, &User.get_cached_by_nickname/1) - User.toggle_confirmation(users) + User.confirm(users) ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"}) diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex index a56641426..747f17e7f 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_file_operation.ex @@ -27,7 +27,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiFileOperation do 422 => Operation.response("Unprocessable Entity", "application/json", ApiError), 404 => Operation.response("Not Found", "application/json", ApiError), 400 => Operation.response("Bad Request", "application/json", ApiError), - 409 => Operation.response("Conflict", "application/json", ApiError) + 409 => Operation.response("Conflict", "application/json", ApiError), + 500 => Operation.response("Error", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex index 79f52dcb3..e576ccbad 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_emoji_pack_operation.ex @@ -169,7 +169,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do responses: %{ 200 => ok_response(), 400 => Operation.response("Bad Request", "application/json", ApiError), - 404 => Operation.response("Not Found", "application/json", ApiError) + 404 => Operation.response("Not Found", "application/json", ApiError), + 500 => Operation.response("Error", "application/json", ApiError) } } end @@ -184,7 +185,8 @@ defmodule Pleroma.Web.ApiSpec.PleromaEmojiPackOperation do parameters: [name_param()], responses: %{ 200 => Operation.response("Metadata", "application/json", metadata()), - 400 => Operation.response("Bad Request", "application/json", ApiError) + 400 => Operation.response("Bad Request", "application/json", ApiError), + 500 => Operation.response("Error", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index 56c024617..30e0a2a55 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Web.Feed.FeedView do def feed_logo do case Pleroma.Config.get([:feed, :logo]) do nil -> - "#{Pleroma.Web.base_url()}/static/logo.png" + "#{Pleroma.Web.base_url()}/static/logo.svg" logo -> "#{Pleroma.Web.base_url()}#{logo}" diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 08f92d55f..20279ff45 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Web.MastoFEController do use Pleroma.Web, :controller alias Pleroma.User + alias Pleroma.Web.MastodonAPI.AuthController + alias Pleroma.Web.OAuth.Token alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -26,27 +28,27 @@ defmodule Pleroma.Web.MastoFEController do ) @doc "GET /web/*path" - def index(%{assigns: %{user: user, token: token}} = conn, _params) - when not is_nil(user) and not is_nil(token) do - conn - |> put_layout(false) - |> render("index.html", - token: token.token, - user: user, - custom_emojis: Pleroma.Emoji.get_all() - ) - end - def index(conn, _params) do - conn - |> put_session(:return_to, conn.request_path) - |> redirect(to: "/web/login") + with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn, + {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do + conn + |> put_layout(false) + |> render("index.html", + token: token.token, + user: user, + custom_emojis: Pleroma.Emoji.get_all() + ) + else + _ -> + conn + |> put_session(:return_to, conn.request_path) + |> redirect(to: "/web/login") + end end @doc "GET /web/manifest.json" def manifest(conn, _params) do - conn - |> render("manifest.json") + render(conn, "manifest.json") end @doc "PUT /api/web/settings: Backend-obscure settings blob for MastoFE, don't parse/reuse elsewhere" diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 7011b7eb1..b4375872b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -25,7 +25,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.MastodonAPI.MastodonAPIController alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.OAuth.OAuthController - alias Pleroma.Web.OAuth.OAuthView alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter @@ -103,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do {:ok, user} <- TwitterAPI.register_user(params), {_, {:ok, token}} <- {:login, OAuthController.login(user, app, app.scopes)} do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + OAuthController.after_token_exchange(conn, %{user: user, token: token}) else {:login, {:account_status, :confirmation_pending}} -> json_response(conn, :ok, %{ diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index 9cc3984d0..93d057a79 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -7,10 +7,13 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.Helpers.AuthHelper + alias Pleroma.Helpers.UriHelper alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken alias Pleroma.Web.TwitterAPI.TwitterAPI action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -20,24 +23,35 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do @local_mastodon_name "Mastodon-Local" @doc "GET /web/login" - def login(%{assigns: %{user: %User{}}} = conn, _params) do - redirect(conn, to: local_mastodon_root_path(conn)) - end - - # Local Mastodon FE login init action - def login(conn, %{"code" => auth_token}) do - with {:ok, app} <- get_or_make_app(), + # Local Mastodon FE login callback action + def login(conn, %{"code" => auth_token} = params) do + with {:ok, app} <- local_mastofe_app(), {:ok, auth} <- Authorization.get_by_token(app, auth_token), - {:ok, token} <- Token.exchange_token(app, auth) do + {:ok, oauth_token} <- Token.exchange_token(app, auth) do + redirect_to = + conn + |> local_mastodon_post_login_path() + |> UriHelper.modify_uri_params(%{"access_token" => oauth_token.token}) + conn - |> put_session(:oauth_token, token.token) - |> redirect(to: local_mastodon_root_path(conn)) + |> AuthHelper.put_session_token(oauth_token.token) + |> redirect(to: redirect_to) + else + _ -> redirect_to_oauth_form(conn, params) + end + end + + def login(conn, params) do + with %{assigns: %{user: %User{}, token: %Token{app_id: app_id}}} <- conn, + {:ok, %{id: ^app_id}} <- local_mastofe_app() do + redirect(conn, to: local_mastodon_post_login_path(conn)) + else + _ -> redirect_to_oauth_form(conn, params) end end - # Local Mastodon FE callback action - def login(conn, _) do - with {:ok, app} <- get_or_make_app() do + defp redirect_to_oauth_form(conn, _params) do + with {:ok, app} <- local_mastofe_app() do path = o_auth_path(conn, :authorize, response_type: "code", @@ -52,9 +66,16 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do @doc "DELETE /auth/sign_out" def logout(conn, _) do - conn - |> clear_session - |> redirect(to: "/") + conn = + with %{assigns: %{token: %Token{} = oauth_token}} <- conn, + session_token = AuthHelper.get_session_token(conn), + {:ok, %Token{token: ^session_token}} <- RevokeToken.revoke(oauth_token) do + AuthHelper.delete_session_token(conn) + else + _ -> conn + end + + redirect(conn, to: "/") end @doc "POST /auth/password" @@ -66,7 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do json_response(conn, :no_content, "") end - defp local_mastodon_root_path(conn) do + defp local_mastodon_post_login_path(conn) do case get_session(conn, :return_to) do nil -> masto_fe_path(conn, :index, ["getting-started"]) @@ -77,9 +98,11 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do end end - @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} - defp get_or_make_app do - %{client_name: @local_mastodon_name, redirect_uris: "."} - |> App.get_or_make(["read", "write", "follow", "push", "admin"]) + @spec local_mastofe_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + def local_mastofe_app do + App.get_or_make( + %{client_name: @local_mastodon_name, redirect_uris: "."}, + ["read", "write", "follow", "push", "admin"] + ) end end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 3158d09ed..026ae9458 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -187,18 +187,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true) following_count = - if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do - user.following_count || 0 - else - 0 - end + if !user.hide_follows_count or !user.hide_follows or opts[:for] == user, + do: user.following_count, + else: 0 followers_count = - if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do - user.follower_count || 0 - else - 0 - end + if !user.hide_followers_count or !user.hide_followers or opts[:for] == user, + do: user.follower_count, + else: 0 bot = user.actor_type == "Service" diff --git a/lib/pleroma/web/o_auth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex index 268ee5b63..e766dcada 100644 --- a/lib/pleroma/web/o_auth/authorization.ex +++ b/lib/pleroma/web/o_auth/authorization.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.OAuth.Authorization do alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token import Ecto.Changeset import Ecto.Query @@ -53,7 +54,8 @@ defmodule Pleroma.Web.OAuth.Authorization do end defp add_lifetime(changeset) do - put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10)) + lifespan = Token.lifespan() + put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan)) end @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t() diff --git a/lib/pleroma/web/o_auth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex index f102c93e7..5d5ec286a 100644 --- a/lib/pleroma/web/o_auth/mfa_controller.ex +++ b/lib/pleroma/web/o_auth/mfa_controller.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.OAuth.MFAController do alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.OAuth.MFAView, as: View alias Pleroma.Web.OAuth.OAuthController - alias Pleroma.Web.OAuth.OAuthView alias Pleroma.Web.OAuth.Token plug(:fetch_session when action in [:show, :verify]) @@ -75,7 +74,7 @@ defmodule Pleroma.Web.OAuth.MFAController do {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token), {:ok, _} <- validates_challenge(user, params), {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + OAuthController.after_token_exchange(conn, %{user: user, token: token}) else _error -> conn diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index d2f9d1ceb..6e3c7e1a1 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do use Pleroma.Web, :controller + alias Pleroma.Helpers.AuthHelper alias Pleroma.Helpers.UriHelper alias Pleroma.Maps alias Pleroma.MFA @@ -79,6 +80,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do available_scopes = (app && app.scopes) || [] scopes = Scopes.fetch_scopes(params, available_scopes) + user = + with %{assigns: %{user: %User{} = user}} <- conn do + user + else + _ -> nil + end + scopes = if scopes == [] do available_scopes @@ -88,6 +96,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template render(conn, Authenticator.auth_template(), %{ + user: user, + app: app && Map.delete(app, :client_secret), response_type: params["response_type"], client_id: params["client_id"], available_scopes: available_scopes, @@ -131,11 +141,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do end end - def create_authorization( - %Plug.Conn{} = conn, - %{"authorization" => _} = params, - opts \\ [] - ) do + def create_authorization(_, _, opts \\ []) + + def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, params, []) do + create_authorization(conn, params, user: user) + end + + def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do after_create_authorization(conn, auth, params) @@ -248,7 +260,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token), {:ok, token} <- RefreshToken.grant(token) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + after_token_exchange(conn, %{user: user, token: token}) else _error -> render_invalid_credentials_error(conn) end @@ -260,7 +272,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:ok, auth} <- Authorization.get_by_token(app, fixed_token), %User{} = user <- User.get_cached_by_id(auth.user_id), {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + after_token_exchange(conn, %{user: user, token: token}) else error -> handle_token_exchange_error(conn, error) @@ -275,7 +287,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:ok, app} <- Token.Utils.fetch_app(conn), requested_scopes <- Scopes.fetch_scopes(params, app.scopes), {:ok, token} <- login(user, app, requested_scopes) do - json(conn, OAuthView.render("token.json", %{user: user, token: token})) + after_token_exchange(conn, %{user: user, token: token}) else error -> handle_token_exchange_error(conn, error) @@ -298,7 +310,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, auth} <- Authorization.create_authorization(app, %User{}), {:ok, token} <- Token.exchange_token(app, auth) do - json(conn, OAuthView.render("token.json", %{token: token})) + after_token_exchange(conn, %{token: token}) else _error -> handle_token_exchange_error(conn, :invalid_credentails) @@ -308,6 +320,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do # Bad request def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do + conn + |> AuthHelper.put_session_token(token.token) + |> json(OAuthView.render("token.json", view_params)) + end + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do conn |> put_status(:forbidden) @@ -361,9 +379,17 @@ defmodule Pleroma.Web.OAuth.OAuthController do render_invalid_credentials_error(conn) end - def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do - with {:ok, app} <- Token.Utils.fetch_app(conn), - {:ok, _token} <- RevokeToken.revoke(app, params) do + def token_revoke(%Plug.Conn{} = conn, %{"token" => token}) do + with {:ok, %Token{} = oauth_token} <- Token.get_by_token(token), + {:ok, oauth_token} <- RevokeToken.revoke(oauth_token) do + conn = + with session_token = AuthHelper.get_session_token(conn), + %Token{token: ^session_token} <- oauth_token do + AuthHelper.delete_session_token(conn) + else + _ -> conn + end + json(conn, %{}) else _error -> diff --git a/lib/pleroma/web/o_auth/o_auth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex index f55247ebd..d22b2f7fe 100644 --- a/lib/pleroma/web/o_auth/o_auth_view.ex +++ b/lib/pleroma/web/o_auth/o_auth_view.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.OAuth.OAuthView do token_type: "Bearer", access_token: token.token, refresh_token: token.refresh_token, - expires_in: expires_in(), + expires_in: NaiveDateTime.diff(token.valid_until, NaiveDateTime.utc_now()), scope: Enum.join(token.scopes, " "), created_at: Utils.format_created_at(token) } @@ -25,6 +25,4 @@ defmodule Pleroma.Web.OAuth.OAuthView do response end end - - defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) end diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex index de37998f2..886117d15 100644 --- a/lib/pleroma/web/o_auth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -27,6 +27,18 @@ defmodule Pleroma.Web.OAuth.Token do timestamps() end + def lifespan do + Pleroma.Config.get!([:oauth2, :token_expires_in]) + end + + @doc "Gets token by unique access token" + @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found} + def get_by_token(token) do + token + |> Query.get_by_token() + |> Repo.find_resource() + end + @doc "Gets token for app by access token" @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} def get_by_token(%App{id: app_id} = _app, token) do @@ -75,11 +87,11 @@ defmodule Pleroma.Web.OAuth.Token do end defp put_valid_until(changeset, attrs) do - expires_in = - Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in())) + valid_until = + Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan())) changeset - |> change(%{valid_until: expires_in}) + |> change(%{valid_until: valid_until}) |> validate_required([:valid_until]) end @@ -130,6 +142,4 @@ defmodule Pleroma.Web.OAuth.Token do end def is_expired?(_), do: false - - defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex index 428c97de6..c15980ff0 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex @@ -42,7 +42,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "pack name, shortcode or filename cannot be empty"}) {:error, _} = error -> - handle_error(conn, error, %{pack_name: pack_name}) + handle_error(conn, error, %{ + pack_name: pack_name, + message: "Unexpected error occurred while adding file to pack." + }) end end @@ -69,7 +72,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "new_shortcode or new_filename cannot be empty"}) {:error, _} = error -> - handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) + handle_error(conn, error, %{ + pack_name: pack_name, + code: shortcode, + message: "Unexpected error occurred while updating." + }) end end @@ -84,7 +91,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "pack name or shortcode cannot be empty"}) {:error, _} = error -> - handle_error(conn, error, %{pack_name: pack_name, code: shortcode}) + handle_error(conn, error, %{ + pack_name: pack_name, + code: shortcode, + message: "Unexpected error occurred while deleting emoji file." + }) end end @@ -94,18 +105,24 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do |> json(%{error: "Emoji \"#{emoji_code}\" does not exist"}) end - defp handle_error(conn, {:error, :not_found}, %{pack_name: pack_name}) do + defp handle_error(conn, {:error, :enoent}, %{pack_name: pack_name}) do conn |> put_status(:not_found) |> json(%{error: "pack \"#{pack_name}\" is not found"}) end - defp handle_error(conn, {:error, _}, _) do - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while adding file to pack." - ) + defp handle_error(conn, {:error, error}, opts) do + message = + [ + Map.get(opts, :message, "Unexpected error occurred."), + Pleroma.Utils.posix_error_message(error) + ] + |> Enum.join(" ") + |> String.trim() + + conn + |> put_status(:internal_server_error) + |> json(%{error: message}) end defp get_filename(%Plug.Upload{filename: filename}), do: filename diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex index a9accc5af..bc4c8d840 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex @@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do with {:ok, pack} <- Pack.show(name: name, page: page, page_size: page_size) do json(conn, pack) else - {:error, :not_found} -> + {:error, :enoent} -> conn |> put_status(:not_found) |> json(%{error: "Pack #{name} does not exist"}) @@ -80,6 +80,17 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do conn |> put_status(:bad_request) |> json(%{error: "pack name cannot be empty"}) + + {:error, error} -> + error_message = + add_posix_error( + "Failed to get the contents of the `#{name}` pack.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) end end @@ -95,7 +106,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do "Pack #{name} cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" }) - {:error, :not_found} -> + {:error, :enoent} -> conn |> put_status(:not_found) |> json(%{error: "Pack #{name} does not exist"}) @@ -116,10 +127,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:internal_server_error) |> json(%{error: "SHA256 for the pack doesn't match the one sent by the server"}) - {:error, e} -> + {:error, error} -> conn |> put_status(:internal_server_error) - |> json(%{error: e}) + |> json(%{error: error}) end end @@ -139,12 +150,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:bad_request) |> json(%{error: "pack name cannot be empty"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while creating pack." - ) + {:error, error} -> + error_message = + add_posix_error( + "Unexpected error occurred while creating pack.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) end end @@ -164,10 +179,12 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:bad_request) |> json(%{error: "pack name cannot be empty"}) - {:error, _, _} -> + {:error, error, _} -> + error_message = add_posix_error("Couldn't delete the `#{name}` pack", error) + conn |> put_status(:internal_server_error) - |> json(%{error: "Couldn't delete the pack #{name}"}) + |> json(%{error: error_message}) end end @@ -180,12 +197,16 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> put_status(:bad_request) |> json(%{error: "The fallback archive does not have all files specified in pack.json"}) - {:error, _} -> - render_error( - conn, - :internal_server_error, - "Unexpected error occurred while updating pack metadata." - ) + {:error, error} -> + error_message = + add_posix_error( + "Unexpected error occurred while updating pack metadata.", + error + ) + + conn + |> put_status(:internal_server_error) + |> json(%{error: error_message}) end end @@ -204,4 +225,10 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do |> json(%{error: "Error accessing emoji pack directory"}) end end + + defp add_posix_error(msg, error) do + [msg, Pleroma.Utils.posix_error_message(error)] + |> Enum.join(" ") + |> String.trim() + end end diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex index d7d4e4092..ff851a874 100644 --- a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex +++ b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex @@ -5,21 +5,14 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do import Plug.Conn + alias Pleroma.Helpers.AuthHelper alias Pleroma.User - alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter def init(options) do options end - def secret_token do - case Pleroma.Config.get(:admin_token) do - blank when blank in [nil, ""] -> nil - token -> token - end - end - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(conn, _) do @@ -30,7 +23,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do end end - def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do + defp authenticate(%{params: %{"admin_token" => admin_token}} = conn) do if admin_token == secret_token() do assign_admin_user(conn) else @@ -38,7 +31,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do end end - def authenticate(conn) do + defp authenticate(conn) do token = secret_token() case get_req_header(conn, "x-admin-token") do @@ -48,10 +41,17 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do end end + defp secret_token do + case Pleroma.Config.get(:admin_token) do + blank when blank in [nil, ""] -> nil + token -> token + end + end + defp assign_admin_user(conn) do conn |> assign(:user, %User{is_admin: true}) - |> OAuthScopesPlug.skip_plug() + |> AuthHelper.skip_oauth() end defp handle_bad_token(conn) do diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index e2a8b1b69..a7b8a9bfe 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -3,6 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.AuthenticationPlug do + @moduledoc "Password authentication plug." + + alias Pleroma.Helpers.AuthHelper alias Pleroma.User import Plug.Conn @@ -11,6 +14,30 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def init(options), do: options + def call(%{assigns: %{user: %User{}}} = conn, _), do: conn + + def call( + %{ + assigns: %{ + auth_user: %{password_hash: password_hash} = auth_user, + auth_credentials: %{password: password} + } + } = conn, + _ + ) do + if checkpw(password, password_hash) do + {:ok, auth_user} = maybe_update_password(auth_user, password) + + conn + |> assign(:user, auth_user) + |> AuthHelper.skip_oauth() + else + conn + end + end + + def call(conn, _), do: conn + def checkpw(password, "$6" <> _ = password_hash) do :crypt.crypt(password, password_hash) == password_hash end @@ -40,40 +67,6 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do - user - |> User.password_update_changeset(%{ - "password" => password, - "password_confirmation" => password - }) - |> Pleroma.Repo.update() + User.reset_password(user, %{password: password, password_confirmation: password}) end - - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - - def call( - %{ - assigns: %{ - auth_user: %{password_hash: password_hash} = auth_user, - auth_credentials: %{password: password} - } - } = conn, - _ - ) do - if checkpw(password, password_hash) do - {:ok, auth_user} = maybe_update_password(auth_user, password) - - conn - |> assign(:user, auth_user) - |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug() - else - conn - end - end - - def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do - Pbkdf2.no_user_verify() - conn - end - - def call(conn, _), do: conn end diff --git a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex index 4dadfb000..97529aedb 100644 --- a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex +++ b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex @@ -3,6 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do + @moduledoc """ + Decodes HTTP Basic Auth information and assigns `:auth_credentials`. + + NOTE: no checks are performed at this step, auth_credentials/username could be easily faked. + """ + import Plug.Conn def init(options) do diff --git a/lib/pleroma/web/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex deleted file mode 100644 index 70d3091f0..000000000 --- a/lib/pleroma/web/plugs/ensure_user_key_plug.ex +++ /dev/null @@ -1,18 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do - import Plug.Conn - - def init(opts) do - opts - end - - def call(%{assigns: %{user: _}} = conn, _), do: conn - - def call(conn, _) do - conn - |> assign(:user, nil) - end -end diff --git a/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex new file mode 100644 index 000000000..4253458b2 --- /dev/null +++ b/lib/pleroma/web/plugs/ensure_user_token_assigns_plug.ex @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug do + import Plug.Conn + + alias Pleroma.Helpers.AuthHelper + alias Pleroma.User + alias Pleroma.Web.OAuth.Token + + @moduledoc "Ensures presence and consistency of :user and :token assigns." + + def init(opts) do + opts + end + + def call(%{assigns: %{user: %User{id: user_id}} = assigns} = conn, _) do + with %Token{user_id: ^user_id} <- assigns[:token] do + conn + else + %Token{} -> + # A safety net for abnormal (unexpected) scenario: :token belongs to another user + AuthHelper.drop_auth_info(conn) + + _ -> + assign(conn, :token, nil) + end + end + + def call(conn, _) do + conn + |> assign(:user, nil) + |> assign(:token, nil) + end +end diff --git a/lib/pleroma/web/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex deleted file mode 100644 index 2a54d0b59..000000000 --- a/lib/pleroma/web/plugs/legacy_authentication_plug.ex +++ /dev/null @@ -1,41 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do - import Plug.Conn - - alias Pleroma.User - - def init(options) do - options - end - - def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - - def call( - %{ - assigns: %{ - auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user, - auth_credentials: %{password: password} - } - } = conn, - _ - ) do - with ^password_hash <- :crypt.crypt(password, password_hash), - {:ok, user} <- - User.reset_password(auth_user, %{password: password, password_confirmation: password}) do - conn - |> assign(:auth_user, user) - |> assign(:user, user) - |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug() - else - _ -> - conn - end - end - - def call(conn, _) do - conn - end -end diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index f44d4dee5..a0a0c5a9b 100644 --- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do + alias Pleroma.Helpers.AuthHelper alias Pleroma.Signature alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils @@ -12,34 +13,16 @@ 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), - {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do - ap_id - else - _ -> - nil - end - end - - defp user_from_key_id(conn) do - with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), - {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do - user - else - _ -> - nil - end - end - - def call(%{assigns: %{user: _}} = conn, _opts), do: conn + def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn # if this has payload make sure it is signed by the same actor that made it def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do with actor_id <- Utils.get_ap_id(actor), {:user, %User{} = user} <- {:user, user_from_key_id(conn)}, {:user_match, true} <- {:user_match, user.ap_id == actor_id} do - assign(conn, :user, user) + conn + |> assign(:user, user) + |> AuthHelper.skip_oauth() else {:user_match, false} -> Logger.debug("Failed to map identity from signature (payload actor mismatch)") @@ -57,7 +40,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do # no payload, probably a signed fetch def call(%{assigns: %{valid_signature: true}} = conn, _opts) do with %User{} = user <- user_from_key_id(conn) do - assign(conn, :user, user) + conn + |> assign(:user, user) + |> AuthHelper.skip_oauth() else _ -> Logger.debug("Failed to map identity from signature (no payload actor mismatch)") @@ -68,4 +53,24 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do # no signature at all def call(conn, _opts), do: conn + + defp key_id_from_conn(conn) do + 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 + end + end + + defp user_from_key_id(conn) do + with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), + {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do + user + else + _ -> + nil + end + end end diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index c7b58d90f..eb287318b 100644 --- a/lib/pleroma/web/plugs/o_auth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -3,9 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.OAuthPlug do + @moduledoc "Performs OAuth authentication by token from params / headers / cookies." + import Plug.Conn import Ecto.Query + alias Pleroma.Helpers.AuthHelper alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.OAuth.App @@ -17,45 +20,26 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do def call(%{assigns: %{user: %User{}}} = conn, _), do: conn - def call(%{params: %{"access_token" => access_token}} = conn, _) do - with {:ok, user, token_record} <- fetch_user_and_token(access_token) do - conn - |> assign(:token, token_record) - |> assign(:user, user) - else - _ -> - # token found, but maybe only with app - with {:ok, app, token_record} <- fetch_app_and_token(access_token) do - conn - |> assign(:token, token_record) - |> assign(:app, app) - else - _ -> conn - end - end - end - def call(conn, _) do - case fetch_token_str(conn) do - {:ok, token} -> - with {:ok, user, token_record} <- fetch_user_and_token(token) do - conn - |> assign(:token, token_record) - |> assign(:user, user) - else - _ -> - # token found, but maybe only with app - with {:ok, app, token_record} <- fetch_app_and_token(token) do - conn - |> assign(:token, token_record) - |> assign(:app, app) - else - _ -> conn - end - end - - _ -> + with {:ok, token_str} <- fetch_token_str(conn) do + with {:ok, user, user_token} <- fetch_user_and_token(token_str), + false <- Token.is_expired?(user_token) do conn + |> assign(:token, user_token) + |> assign(:user, user) + else + _ -> + with {:ok, app, app_token} <- fetch_app_and_token(token_str), + false <- Token.is_expired?(app_token) do + conn + |> assign(:token, app_token) + |> assign(:app, app) + else + _ -> conn + end + end + else + _ -> conn end end @@ -70,7 +54,6 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do preload: [user: user] ) - # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength with %Token{user: user} = token_record <- Repo.one(query) do {:ok, user, token_record} end @@ -86,29 +69,23 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do end end - # Gets token from session by :oauth_token key + # Gets token string from conn (in params / headers / session) # - @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_from_session(conn) do - case get_session(conn, :oauth_token) do - nil -> :no_token_found - token -> {:ok, token} - end + @spec fetch_token_str(Plug.Conn.t() | list(String.t())) :: :no_token_found | {:ok, String.t()} + defp fetch_token_str(%Plug.Conn{params: %{"access_token" => access_token}} = _conn) do + {:ok, access_token} end - # Gets token from headers - # - @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} defp fetch_token_str(%Plug.Conn{} = conn) do headers = get_req_header(conn, "authorization") - with :no_token_found <- fetch_token_str(headers), - do: fetch_token_from_session(conn) + with {:ok, token} <- fetch_token_str(headers) do + {:ok, token} + else + _ -> fetch_token_from_session(conn) + end end - @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()} - defp fetch_token_str([]), do: :no_token_found - defp fetch_token_str([token | tail]) do trimmed_token = String.trim(token) @@ -117,4 +94,14 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do _ -> fetch_token_str(tail) end end + + defp fetch_token_str([]), do: :no_token_found + + @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()} + defp fetch_token_from_session(conn) do + case AuthHelper.get_session_token(conn) do + nil -> :no_token_found + token -> {:ok, token} + end + end end diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex index cfc30837c..e6d398b14 100644 --- a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do import Pleroma.Web.Gettext alias Pleroma.Config + alias Pleroma.Helpers.AuthHelper use Pleroma.Web, :plug @@ -28,7 +29,7 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do conn options[:fallback] == :proceed_unauthenticated -> - drop_auth_info(conn) + AuthHelper.drop_auth_info(conn) true -> missing_scopes = scopes -- matched_scopes @@ -44,15 +45,6 @@ defmodule Pleroma.Web.Plugs.OAuthScopesPlug do end end - @doc "Drops authentication info from connection" - def drop_auth_info(conn) do - # To simplify debugging, setting a private variable on `conn` if auth info is dropped - conn - |> put_private(:authentication_ignored, true) - |> assign(:user, nil) - |> assign(:token, nil) - end - @doc "Keeps those of `scopes` which are descendants of `supported_scopes`" def filter_descendants(scopes, supported_scopes) do Enum.filter( diff --git a/lib/pleroma/web/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex deleted file mode 100644 index 6e176d553..000000000 --- a/lib/pleroma/web/plugs/session_authentication_plug.ex +++ /dev/null @@ -1,21 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do - import Plug.Conn - - def init(options) do - options - end - - def call(conn, _) do - with saved_user_id <- get_session(conn, :user_id), - %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do - conn - |> assign(:user, conn.assigns.auth_user) - else - _ -> conn - end - end -end diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex index e520159e4..9f4a6b6ac 100644 --- a/lib/pleroma/web/plugs/set_user_session_id_plug.ex +++ b/lib/pleroma/web/plugs/set_user_session_id_plug.ex @@ -3,16 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do - import Plug.Conn - alias Pleroma.User + alias Pleroma.Helpers.AuthHelper + alias Pleroma.Web.OAuth.Token def init(opts) do opts end - def call(%{assigns: %{user: %User{id: id}}} = conn, _) do - conn - |> put_session(:user_id, id) + def call(%{assigns: %{token: %Token{} = oauth_token}} = conn, _) do + AuthHelper.put_session_token(conn, oauth_token.token) end def call(conn, _), do: conn diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex index fa28ee48b..4f1b163bd 100644 --- a/lib/pleroma/web/plugs/user_enabled_plug.ex +++ b/lib/pleroma/web/plugs/user_enabled_plug.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.UserEnabledPlug do - import Plug.Conn + alias Pleroma.Helpers.AuthHelper alias Pleroma.User def init(options) do @@ -11,9 +11,10 @@ defmodule Pleroma.Web.Plugs.UserEnabledPlug do end def call(%{assigns: %{user: %User{} = user}} = conn, _) do - case User.account_status(user) do - :active -> conn - _ -> assign(conn, :user, nil) + if User.account_status(user) == :active do + conn + else + AuthHelper.drop_auth_info(conn) end end diff --git a/lib/pleroma/web/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex index 4039600da..89e16b49f 100644 --- a/lib/pleroma/web/plugs/user_fetcher_plug.ex +++ b/lib/pleroma/web/plugs/user_fetcher_plug.ex @@ -3,6 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.UserFetcherPlug do + @moduledoc """ + Assigns `:auth_user` basing on `:auth_credentials`. + + NOTE: no checks are performed at this step, auth_credentials/username could be easily faked. + """ + alias Pleroma.User import Plug.Conn diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 75a885377..aefc9f0be 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -34,6 +34,7 @@ defmodule Pleroma.Web.Router do plug(:fetch_session) plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.UserEnabledPlug) + plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) end pipeline :expect_authentication do @@ -48,15 +49,13 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.OAuthPlug) plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug) plug(Pleroma.Web.Plugs.UserFetcherPlug) - plug(Pleroma.Web.Plugs.SessionAuthenticationPlug) - plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug) plug(Pleroma.Web.Plugs.AuthenticationPlug) end pipeline :after_auth do plug(Pleroma.Web.Plugs.UserEnabledPlug) plug(Pleroma.Web.Plugs.SetUserSessionIdPlug) - plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) end pipeline :base_api do @@ -100,7 +99,7 @@ defmodule Pleroma.Web.Router do pipeline :pleroma_html do plug(:browser) plug(:authenticate) - plug(Pleroma.Web.Plugs.EnsureUserKeyPlug) + plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug) end pipeline :well_known do @@ -292,7 +291,6 @@ defmodule Pleroma.Web.Router do post("/main/ostatus", UtilController, :remote_subscribe) get("/ostatus_subscribe", RemoteFollowController, :follow) - post("/ostatus_subscribe", RemoteFollowController, :do_follow) end @@ -321,19 +319,25 @@ defmodule Pleroma.Web.Router do end scope "/oauth", Pleroma.Web.OAuth do + get("/registration_details", OAuthController, :registration_details) + + post("/mfa/verify", MFAController, :verify, as: :mfa_verify) + get("/mfa", MFAController, :show) + scope [] do pipe_through(:oauth) + get("/authorize", OAuthController, :authorize) + post("/authorize", OAuthController, :create_authorization) end - post("/authorize", OAuthController, :create_authorization) - post("/token", OAuthController, :token_exchange) - post("/revoke", OAuthController, :token_revoke) - get("/registration_details", OAuthController, :registration_details) + scope [] do + pipe_through(:fetch_session) - post("/mfa/challenge", MFAController, :challenge) - post("/mfa/verify", MFAController, :verify, as: :mfa_verify) - get("/mfa", MFAController, :show) + post("/token", OAuthController, :token_exchange) + post("/revoke", OAuthController, :token_revoke) + post("/mfa/challenge", MFAController, :challenge) + end scope [] do pipe_through(:browser) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 71fe27c89..7d4a1304a 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -36,9 +36,8 @@ defmodule Pleroma.Web.Streamer do ) :: {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do - case get_topic(stream, user, oauth_token, params) do - {:ok, topic} -> add_socket(topic, user) - error -> error + with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do + add_socket(topic, user) end end @@ -70,10 +69,10 @@ defmodule Pleroma.Web.Streamer do def get_topic( stream, %User{id: user_id} = user, - %Token{user_id: token_user_id} = oauth_token, + %Token{user_id: user_id} = oauth_token, _params ) - when stream in @user_streams and user_id == token_user_id do + when stream in @user_streams do # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope) required_scopes = if stream == "user:notification" do @@ -97,10 +96,9 @@ defmodule Pleroma.Web.Streamer do def get_topic( "list", %User{id: user_id} = user, - %Token{user_id: token_user_id} = oauth_token, + %Token{user_id: user_id} = oauth_token, %{"list" => id} - ) - when user_id == token_user_id do + ) do cond do OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] -> {:error, :unauthorized} @@ -137,16 +135,10 @@ defmodule Pleroma.Web.Streamer do def stream(topics, items) do if should_env_send?() do - List.wrap(topics) - |> Enum.each(fn topic -> - List.wrap(items) - |> Enum.each(fn item -> - spawn(fn -> do_stream(topic, item) end) - end) - end) + for topic <- List.wrap(topics), item <- List.wrap(items) do + spawn(fn -> do_stream(topic, item) end) + end end - - :ok end def filtered_by_user?(user, item, streamed_type \\ :activity) @@ -160,8 +152,7 @@ defmodule Pleroma.Web.Streamer do domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) with parent <- Object.normalize(item) || item, - true <- - Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), + true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, true <- !(streamed_type == :activity && item.data["type"] == "Announce" && @@ -195,6 +186,19 @@ defmodule Pleroma.Web.Streamer do end) end + defp do_stream("follow_relationship", item) do + text = StreamerView.render("follow_relationships_update.json", item) + user_topic = "user:#{item.follower.id}" + + Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n") + + Registry.dispatch(@registry, user_topic, fn list -> + Enum.each(list, fn {pid, _auth} -> + send(pid, {:text, text}) + end) + end) + end + defp do_stream("participation", participation) do user_topic = "direct:#{participation.user_id}" Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n") diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex index 860df5f9c..60eceff22 100644 --- a/lib/pleroma/web/templates/email/digest.html.eex +++ b/lib/pleroma/web/templates/email/digest.html.eex @@ -126,7 +126,7 @@ <div align="center" class="img-container center" style="padding-right: 0px;padding-left: 0px;"> <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img - align="center" alt="Image" border="0" class="center" src="cid:logo.png" + align="center" alt="Image" border="0" class="center" src="cid:logo.svg" style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;" title="Image" height="80" /> <!--[if mso]></td></tr></table><![endif]--> diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 3f28f1920..1ede59fd8 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -1,233 +1,19 @@ <!DOCTYPE html> -<html> +<html lang="en"> <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" /> - <title> - <%= Pleroma.Config.get([:instance, :name]) %> - </title> - <style> - body { - background-color: #121a24; - font-family: sans-serif; - color: #b9b9ba; - text-align: center; - } - - .container { - max-width: 420px; - padding: 20px; - background-color: #182230; - border-radius: 4px; - margin: auto; - margin-top: 10vh; - box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5); - } - - h1 { - margin: 0; - font-size: 24px; - } - - h2 { - color: #b9b9ba; - font-weight: normal; - font-size: 18px; - margin-bottom: 20px; - } - - a { - color: #d8a070; - text-decoration: none; - } - - form { - width: 100%; - } - - .input { - text-align: left; - color: #89898a; - display: flex; - flex-direction: column; - } - - input { - box-sizing: content-box; - padding: 10px; - margin-top: 5px; - margin-bottom: 10px; - background-color: #121a24; - color: #b9b9ba; - border: 0; - transition-property: border-bottom; - transition-duration: 0.35s; - border-bottom: 2px solid #2a384a; - font-size: 14px; - } - - .scopes-input { - display: flex; - flex-direction: column; - margin-top: 1em; - text-align: left; - color: #89898a; - } - - .scopes-input label:first-child { - height: 2em; - } - - .scopes { - display: flex; - flex-wrap: wrap; - text-align: left; - color: #b9b9ba; - } - - .scope { - display: flex; - flex-basis: 100%; - height: 2em; - align-items: center; - } - - .scope:before { - color: #b9b9ba; - content: "✔\fe0e"; - margin-left: 1em; - margin-right: 1em; - } - - [type="checkbox"] + label { - display: none; - cursor: pointer; - margin: 0.5em; - } - - [type="checkbox"] { - display: none; - } - - [type="checkbox"] + label:before { - cursor: pointer; - display: inline-block; - color: white; - background-color: #121a24; - border: 4px solid #121a24; - box-shadow: 0px 0px 1px 0 #d8a070; - box-sizing: border-box; - width: 1.2em; - height: 1.2em; - margin-right: 1.0em; - content: ""; - transition-property: background-color; - transition-duration: 0.35s; - color: #121a24; - margin-bottom: -0.2em; - border-radius: 2px; - } - - [type="checkbox"]:checked + label:before { - background-color: #d8a070; - } - - input:focus { - outline: none; - border-bottom: 2px solid #d8a070; - } - - button { - box-sizing: border-box; - width: 100%; - background-color: #1c2a3a; - color: #b9b9ba; - border-radius: 4px; - border: none; - padding: 10px; - margin-top: 20px; - margin-bottom: 20px; - text-transform: uppercase; - font-size: 16px; - box-shadow: 0px 0px 2px 0px black, - 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, - 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; - } - - button:hover { - cursor: pointer; - box-shadow: 0px 0px 0px 1px #d8a070, - 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, - 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; - } - - .alert-danger { - box-sizing: border-box; - width: 100%; - background-color: #931014; - border: 1px solid #a06060; - border-radius: 4px; - padding: 10px; - margin-top: 20px; - font-weight: 500; - font-size: 16px; - } - - .alert-info { - box-sizing: border-box; - width: 100%; - border-radius: 4px; - border: 1px solid #7d796a; - padding: 10px; - margin-top: 20px; - font-weight: 500; - font-size: 16px; - } - - @media all and (max-width: 440px) { - .container { - margin-top: 0 - } - - .scope { - flex-basis: 0%; - } - - .scope:before { - content: ""; - margin-left: 0em; - margin-right: 1em; - } - - .scope:first-child:before { - margin-left: 1em; - content: "✔\fe0e"; - } - - .scope:after { - content: ","; - } - - .scope:last-child:after { - content: ""; - } - } - .form-row { - display: flex; - } - .form-row > label { - text-align: left; - line-height: 47px; - flex: 1; - } - .form-row > input { - flex: 2; - } - </style> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui"> + <title><%= Pleroma.Config.get([:instance, :name]) %></title> + <link rel="stylesheet" href="/instance/static.css"> </head> <body> + <div class="instance-header"> + <a class="instance-header__content" href="/"> + <img class="instance-header__thumbnail" src="<%= Pleroma.Config.get([:instance, :instance_thumbnail]) %>"> + <h1 class="instance-header__title"><%= Pleroma.Config.get([:instance, :name]) %></h1> + </a> + </div> <div class="container"> - <h1><%= Pleroma.Config.get([:instance, :name]) %></h1> <%= @inner_content %> </div> </body> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index b17142ff8..1a85818ec 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -5,32 +5,55 @@ <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> <% end %> -<h2>OAuth Authorization</h2> <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> -<%= if @params["registration"] in ["true", true] do %> - <h3>This is the first time you visit! Please enter your Pleroma handle.</h3> - <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p> - <div class="input"> - <%= label f, :nickname, "Pleroma Handle" %> - <%= text_input f, :nickname, placeholder: "lain" %> +<%= if @user do %> + <div class="account-header"> + <div class="account-header__banner" style="background-image: url('<%= Pleroma.User.banner_url(@user) %>')"></div> + <div class="account-header__avatar" style="background-image: url('<%= Pleroma.User.avatar_url(@user) %>')"></div> + <div class="account-header__meta"> + <div class="account-header__display-name"><%= @user.name %></div> + <div class="account-header__nickname">@<%= @user.nickname %>@<%= Pleroma.User.get_host(@user) %></div> + </div> </div> - <%= hidden_input f, :name, value: @params["name"] %> - <%= hidden_input f, :password, value: @params["password"] %> - <br> -<% else %> - <div class="input"> - <%= label f, :name, "Username" %> - <%= text_input f, :name %> - </div> - <div class="input"> - <%= label f, :password, "Password" %> - <%= password_input f, :password %> - </div> - <%= submit "Log In" %> - <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> <% end %> +<div class="container__content"> + <%= if @app do %> + <p>Application <strong><%= @app.client_name %></strong> is requesting access to your account.</p> + <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> + <% end %> + + <%= if @user do %> + <div class="actions"> + <a class="button button--cancel" href="/">Cancel</a> + <%= submit "Approve", class: "button--approve" %> + </div> + <% else %> + <%= if @params["registration"] in ["true", true] do %> + <h3>This is the first time you visit! Please enter your Pleroma handle.</h3> + <p>Choose carefully! You won't be able to change this later. You will be able to change your display name, though.</p> + <div class="input"> + <%= label f, :nickname, "Pleroma Handle" %> + <%= text_input f, :nickname, placeholder: "lain" %> + </div> + <%= hidden_input f, :name, value: @params["name"] %> + <%= hidden_input f, :password, value: @params["password"] %> + <br> + <% else %> + <div class="input"> + <%= label f, :name, "Username" %> + <%= text_input f, :name %> + </div> + <div class="input"> + <%= label f, :password, "Password" %> + <%= password_input f, :password %> + </div> + <%= submit "Log In" %> + <% end %> + <% end %> +</div> + <%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :response_type, value: @response_type %> <%= hidden_input f, :redirect_uri, value: @redirect_uri %> @@ -40,4 +63,3 @@ <%= if Pleroma.Config.oauth_consumer_enabled?() do %> <%= render @view_module, Pleroma.Web.Auth.Authenticator.oauth_consumer_template(), assigns %> <% end %> - diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/twitter_api/controller.ex index f42dba442..16f43863c 100644 --- a/lib/pleroma/web/twitter_api/controller.ex +++ b/lib/pleroma/web/twitter_api/controller.ex @@ -31,10 +31,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def confirm_email(conn, %{"user_id" => uid, "token" => token}) do with %User{} = user <- User.get_cached_by_id(uid), true <- user.local and user.confirmation_pending and user.confirmation_token == token, - {:ok, _} <- - user - |> User.confirmation_changeset(need_confirmation: false) - |> User.update_and_set_cache() do + {:ok, _} <- User.confirm(user) do redirect(conn, to: "/") end end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 5d7948507..8e20b0d55 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -45,7 +45,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do case User.register(changeset) do {:ok, user} -> - maybe_notify_admins(user) {:ok, user} {:error, changeset} -> @@ -58,18 +57,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end end - defp maybe_notify_admins(%User{} = account) do - if Pleroma.Config.get([:instance, :account_approval_required]) do - User.all_superusers() - |> Enum.filter(fn user -> not is_nil(user.email) end) - |> Enum.each(fn superuser -> - superuser - |> Pleroma.Emails.AdminEmail.new_unapproved_registration(account) - |> Pleroma.Emails.Mailer.deliver_async() - end) - end - end - def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), %User{local: true, email: email, deactivated: false} = user when is_binary(email) <- diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index 476a33245..4fc14166d 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -74,6 +74,28 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end + def render("follow_relationships_update.json", item) do + %{ + event: "pleroma:follow_relationships_update", + payload: + %{ + state: item.state, + follower: %{ + id: item.follower.id, + follower_count: item.follower.follower_count, + following_count: item.follower.following_count + }, + following: %{ + id: item.following.id, + follower_count: item.following.follower_count, + following_count: item.following.following_count + } + } + |> Jason.encode!() + } + |> Jason.encode!() + end + def render("conversation.json", %Participation{} = participation) do %{ event: "conversation", @@ -22,7 +22,7 @@ defmodule Pleroma.Mixfile do docs: [ source_url_pattern: "https://git.pleroma.social/pleroma/pleroma/blob/develop/%{path}#L%{line}", - logo: "priv/static/static/logo.png", + logo: "priv/static/images/logo.png", extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"), groups_for_extras: [ "Installation manuals": Path.wildcard("docs/installation/*.md"), @@ -194,7 +194,8 @@ defmodule Pleroma.Mixfile do ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, {:restarter, path: "./restarter"}, {:majic, - git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", branch: "develop"}, + git: "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", + ref: "4c692e544b28d1f5e543fb8a44be090f8cd96f80"}, {:open_api_spex, git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"}, @@ -206,7 +207,10 @@ defmodule Pleroma.Mixfile do {:mock, "~> 0.3.5", only: :test}, # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed {:excoveralls, "0.12.3", only: :test}, - {:hackney, "1.15.2", override: true}, + {:hackney, + git: "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", + ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e", + override: true}, {:mox, "~> 0.5", only: :test}, {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} ] ++ oauth_deps() @@ -11,7 +11,7 @@ "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "e0f16822d578866e186a0974d65ad58cddc1e2ab", [ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"]}, "castore": {:hex, :castore, "0.1.7", "1ca19eee705cde48c9e809e37fdd0730510752cc397745e550f6065a56a701e9", [:mix], [], "hexpm", "a2ae2c13d40e9c308387f1aceb14786dca019ebc2a11484fb2a9f797ea0aa0d8"}, - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, + "certifi": {:git, "https://github.com/certifi/erlang-certifi", "e08b12e8993502240c25b78563993776f87ecd2a", [tag: "2.5.1"]}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "comeonin": {:hex, :comeonin, "5.3.1", "7fe612b739c78c9c1a75186ef2d322ce4d25032d119823269d0aa1e2f1e20025", [:mix], [], "hexpm", "d6222483060c17f0977fad1b7401ef0c5863c985a64352755f366aee3799c245"}, "concurrent_limiter": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/concurrent_limiter.git", "d81be41024569330f296fc472e24198d7499ba78", [ref: "d81be41024569330f296fc472e24198d7499ba78"]}, @@ -53,12 +53,12 @@ "gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"}, "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, "gun": {:git, "https://github.com/ninenines/gun.git", "921c47146b2d9567eac7e9a4d2ccc60fffd4f327", [ref: "921c47146b2d9567eac7e9a4d2ccc60fffd4f327"]}, - "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, + "hackney": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/hackney.git", "7d7119f0651515d6d7669c78393fd90950a3ec6e", [ref: "7d7119f0651515d6d7669c78393fd90950a3ec6e"]}, "html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"}, "httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"}, - "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"}, + "idna": {:git, "https://github.com/benoitc/erlang-idna", "6cff72747821110169ecfac871b0c69e5064afff", [tag: "6.0.0"]}, "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, @@ -66,13 +66,13 @@ "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "libring": {:hex, :libring, "1.4.0", "41246ba2f3fbc76b3971f6bce83119dfec1eee17e977a48d8a9cfaaf58c2a8d6", [:mix], [], "hexpm"}, "linkify": {:hex, :linkify, "0.4.0", "7845b6ac33050a41acaf9318923ce6e7f3854418be9a5f22184de103f7a68ff9", [:mix], [], "hexpm", "a0ceb4c78591fecccf1d99fecc10c13dba75a307c663c80e28af9e2cdd9776ee"}, - "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [branch: "develop"]}, + "majic": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/majic.git", "4c692e544b28d1f5e543fb8a44be090f8cd96f80", [ref: "4c692e544b28d1f5e543fb8a44be090f8cd96f80"]}, "makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"}, "makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "metrics": {:git, "https://github.com/benoitc/erlang-metrics", "c6eb4dcf29f9e907539915e2ab996f40c2ec7e8e", [tag: "1.0.1"]}, "mime": {:hex, :mime, "1.4.0", "5066f14944b470286146047d2f73518cf5cca82f8e4815cf35d196b58cf07c47", [:mix], [], "hexpm", "75fa42c4228ea9a23f70f123c74ba7cece6a03b1fd474fe13f6a7a85c6ea4ff6"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "mimerl": {:git, "https://github.com/benoitc/mimerl", "5a1b22a8fada5b3b40438da00a6923cb87a42bbc", [tag: "1.2.0"]}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.5", "feb81f52b8dcf0a0d65001d2fec459f6b6a8c22562d94a965862f6cc066b5431", [:mix], [{:meck, "~> 0.8.13", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "6fae404799408300f863550392635d8f7e3da6b71abdd5c393faf41b131c8728"}, "mogrify": {:hex, :mogrify, "0.7.4", "9b2496dde44b1ce12676f85d7dc531900939e6367bc537c7243a1b089435b32d", [:mix], [], "hexpm", "50d79e337fba6bc95bfbef918058c90f50b17eed9537771e61d4619488f099c3"}, @@ -84,7 +84,7 @@ "oban": {:hex, :oban, "2.1.0", "034144686f7e76a102b5d67731f098d98a9e4a52b07c25ad580a01f83a7f1cf5", [:mix], [{:ecto_sql, ">= 3.4.3", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c6f067fa3b308ed9e0e6beb2b34277c9c4e48bf95338edabd8f4a757a26e04c2"}, "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "f296ac0924ba3cf79c7a588c4c252889df4c2edd", [ref: "f296ac0924ba3cf79c7a588c4c252889df4c2edd"]}, "p1_utils": {:hex, :p1_utils, "1.0.18", "3fe224de5b2e190d730a3c5da9d6e8540c96484cf4b4692921d1e28f0c32b01c", [:rebar3], [], "hexpm", "1fc8773a71a15553b179c986b22fbeead19b28fe486c332d4929700ffeb71f88"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "parse_trans": {:git, "https://github.com/uwiger/parse_trans.git", "76abb347c3c1d00fb0ccf9e4b43e22b3d2288484", [tag: "3.3.0"]}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "1.2.1", "9cbe354b58121075bd20eb83076900a3832324b7dd171a6895fab57b6bb2752c", [:mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}], "hexpm", "d3b40a4a4630f0b442f19eca891fcfeeee4c40871936fed2f68e1c4faa30481f"}, "phoenix": {:hex, :phoenix, "1.5.6", "8298cdb4e0f943242ba8410780a6a69cbbe972fef199b341a36898dd751bdd66", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0dc4d39af1306b6aa5122729b0a95ca779e42c708c6fe7abbb3d336d5379e956"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.2.1", "13f124cf0a3ce0f1948cf24654c7b9f2347169ff75c1123f44674afee6af3b03", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "478a1bae899cac0a6e02be1deec7e2944b7754c04e7d4107fc5a517f877743c0"}, @@ -110,7 +110,7 @@ "recon": {:hex, :recon, "2.5.1", "430ffa60685ac1efdfb1fe4c97b8767c92d0d92e6e7c3e8621559ba77598678a", [:mix, :rebar3], [], "hexpm", "5721c6b6d50122d8f68cccac712caa1231f97894bab779eff5ff0f886cb44648"}, "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8", [ref: "b647d0deecaa3acb140854fe4bda5b7e1dc6d1c8"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, + "ssl_verify_fun": {:git, "https://github.com/deadtrickster/ssl_verify_fun.erl", "c5718226b0b9f3d1a38ef6ca3c3b4c75f53dda92", [tag: "1.1.4"]}, "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"}, "swoosh": {:hex, :swoosh, "1.0.6", "6765e334c67dacabe721f0d701c7e5a6f06e4595c90df6f91e73ebd54d555833", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "7c50ef78e4acfd1cbd4907dc1fa87b5540675a6be9dc979d04890f49d7ec1830"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, @@ -120,7 +120,7 @@ "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.0.4", "a3baa4709ea8dba552dca165af6ae97c624a2d6ac14bd265165eaa8e8af94af6", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "b02637db3df1fd66dd2d3c4f194a81633d0e4b44308d36c1b2fdfd1e4e6f169b"}, "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, + "unicode_util_compat": {:git, "https://github.com/benoitc/unicode_util_compat.git", "38d7bc105f51159e8ea3279c40121db9db1e652f", [tag: "0.3.1"]}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.0", "598b5135e696fd1404dc8d0d7c0fa2c027244a4e5d5e5a98ba267f14fdeaabc8", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "f10bdd1afe527ede694749fb77a2f22f146a51b054c7fa541c9fd920fba7c875"}, "websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, diff --git a/priv/gettext/en/LC_MESSAGES/posix_errors.po b/priv/gettext/en/LC_MESSAGES/posix_errors.po new file mode 100644 index 000000000..4d8fbf1d3 --- /dev/null +++ b/priv/gettext/en/LC_MESSAGES/posix_errors.po @@ -0,0 +1,141 @@ +## This file is a PO Template file. +msgid "eperm" +msgstr "Operation not permitted" + +msgid "eacces" +msgstr "Permission denied" + +msgid "eagain" +msgstr "Resource temporarily unavailable" + +msgid "ebadf" +msgstr "Bad file descriptor" + +msgid "ebadmsg" +msgstr "Bad message" + +msgid "ebusy" +msgstr "Device or resource busy" + +msgid "edeadlk" +msgstr "Resource deadlock avoided" + +msgid "edeadlock" +msgstr "Resource deadlock avoided" + +msgid "edquot" +msgstr "Disk quota exceeded" + +msgid "eexist" +msgstr "File exists" + +msgid "efault" +msgstr "Bad address" + +msgid "efbig" +msgstr "File is too large" + +msgid "eftype" +msgstr "Inappropriate file type or format" + +msgid "eintr" +msgstr "Interrupted system call" + +msgid "einval" +msgstr "Invalid argument" + +msgid "eio" +msgstr "Input/output error" + +msgid "eisdir" +msgstr "Illegal operation on a directory" + +msgid "eloop" +msgstr "Too many levels of symbolic links" + +msgid "emfile" +msgstr "Too many open files" + +msgid "emlink" +msgstr "Too many links" + +msgid "emultihop" +msgstr "Multihop attempted" + +msgid "enametoolong" +msgstr "File name is too long" + +msgid "enfile" +msgstr "Too many open files in system" + +msgid "enobufs" +msgstr "No buffer space available" + +msgid "enodev" +msgstr "No such device" + +msgid "enolck" +msgstr "No locks available" + +msgid "enolink" +msgstr "Link has been severed" + +msgid "enoent" +msgstr "No such file or directory" + +msgid "enomem" +msgstr "Cannot allocate memory" + +msgid "enospc" +msgstr "No space left on device" + +msgid "enosr" +msgstr "Out of streams resources" + +msgid "enostr" +msgstr "Device is not a stream" + +msgid "enosys" +msgstr "Function not implemented" + +msgid "enotblk" +msgstr "Block device required" + +msgid "enotdir" +msgstr "Not a directory" + +msgid "enotsup" +msgstr "Operation not supported" + +msgid "enxio" +msgstr "No such device or address" + +msgid "eopnotsupp" +msgstr "Operation not supported" + +msgid "eoverflow" +msgstr "Value too large for defined data type" + +msgid "epipe" +msgstr "Broken pipe" + +msgid "erange" +msgstr "Numerical result out of range" + +msgid "erofs" +msgstr "Read-only file system" + +msgid "espipe" +msgstr "Illegal seek" + +msgid "esrch" +msgstr "No such process" + +msgid "estale" +msgstr "Stale file handle" + +msgid "etxtbsy" +msgstr "Text file busy" + +msgid "exdev" +msgstr "Invalid cross-device link" diff --git a/priv/gettext/posix_errors.pot b/priv/gettext/posix_errors.pot new file mode 100644 index 000000000..c9f593944 --- /dev/null +++ b/priv/gettext/posix_errors.pot @@ -0,0 +1,149 @@ +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +msgid "eperm" +msgstr "" + +msgid "eacces" +msgstr "" + +msgid "eagain" +msgstr "" + +msgid "ebadf" +msgstr "" + +msgid "ebadmsg" +msgstr "" + +msgid "ebusy" +msgstr "" + +msgid "edeadlk" +msgstr "" + +msgid "edeadlock" +msgstr "" + +msgid "edquot" +msgstr "" + +msgid "eexist" +msgstr "" + +msgid "efault" +msgstr "" + +msgid "efbig" +msgstr "" + +msgid "eftype" +msgstr "" + +msgid "eintr" +msgstr "" + +msgid "einval" +msgstr "" + +msgid "eio" +msgstr "" + +msgid "eisdir" +msgstr "" + +msgid "eloop" +msgstr "" + +msgid "emfile" +msgstr "" + +msgid "emlink" +msgstr "" + +msgid "emultihop" +msgstr "" + +msgid "enametoolong" +msgstr "" + +msgid "enfile" +msgstr "" + +msgid "enobufs" +msgstr "" + +msgid "enodev" +msgstr "" + +msgid "enolck" +msgstr "" + +msgid "enolink" +msgstr "" + +msgid "enoent" +msgstr "" + +msgid "enomem" +msgstr "" + +msgid "enospc" +msgstr "" + +msgid "enosr" +msgstr "" + +msgid "enostr" +msgstr "" + +msgid "enosys" +msgstr "" + +msgid "enotblk" +msgstr "" + +msgid "enotdir" +msgstr "" + +msgid "enotsup" +msgstr "" + +msgid "enxio" +msgstr "" + +msgid "eopnotsupp" +msgstr "" + +msgid "eoverflow" +msgstr "" + +msgid "epipe" +msgstr "" + +msgid "erange" +msgstr "" + +msgid "erofs" +msgstr "" + +msgid "espipe" +msgstr "" + +msgid "esrch" +msgstr "" + +msgid "estale" +msgstr "" + +msgid "etxtbsy" +msgstr "" + +msgid "exdev" +msgstr "" diff --git a/priv/gettext/uk/LC_MESSAGES/errors.po b/priv/gettext/uk/LC_MESSAGES/errors.po new file mode 100644 index 000000000..9638761ec --- /dev/null +++ b/priv/gettext/uk/LC_MESSAGES/errors.po @@ -0,0 +1,599 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-12-10 16:09+0000\n" +"PO-Revision-Date: 2020-12-11 00:56+0000\n" +"Last-Translator: ZEN <xinit.info@gmail.com>\n" +"Language-Team: Ukrainian <https://translate.pleroma.social/projects/pleroma/" +"pleroma/uk/>\n" +"Language: uk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=" +"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.0.4\n" + +## This file is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here as no +## effect: edit them in PO (`.po`) files instead. +## From Ecto.Changeset.cast/4 +msgid "can't be blank" +msgstr "не може бути пустим" + +## From Ecto.Changeset.unique_constraint/3 +msgid "has already been taken" +msgstr "вже зайнято" + +## From Ecto.Changeset.put_change/3 +msgid "is invalid" +msgstr "недійсний" + +## From Ecto.Changeset.validate_format/3 +msgid "has invalid format" +msgstr "має недійсний формат" + +## From Ecto.Changeset.validate_subset/3 +msgid "has an invalid entry" +msgstr "має недійсний запис" + +## From Ecto.Changeset.validate_exclusion/3 +msgid "is reserved" +msgstr "зарезервовано" + +## From Ecto.Changeset.validate_confirmation/3 +msgid "does not match confirmation" +msgstr "не збігається з підтвердженням" + +## From Ecto.Changeset.no_assoc_constraint/3 +msgid "is still associated with this entry" +msgstr "все ще пов'язаний з цим записом" + +msgid "are still associated with this entry" +msgstr "все ще пов'язані з цим записом" + +## From Ecto.Changeset.validate_length/3 +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "повинен містити %{count} символ" +msgstr[1] "повинен містити %{count} символи" +msgstr[2] "повинен містити %{count} символів" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "повинен містити %{count} елемент" +msgstr[1] "повинен містити %{count} елементи" +msgstr[2] "повинен містити %{count} елементів" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "повинен містити хоча б %{count} символ" +msgstr[1] "повинен містити хоча б %{count} символи" +msgstr[2] "повинен містити хоча б %{count} символів" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "повинен містити хоча б %{count} елемент" +msgstr[1] "повинен містити хоча б %{count} елементи" +msgstr[2] "повинен містити хоча б %{count} елементів" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "повинен бути не більше %{count} символу" +msgstr[1] "повинен бути не більше %{count} символів" +msgstr[2] "повинен бути не більше %{count} символів" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "повинен містити не більше %{count} елемента" +msgstr[1] "повинен містити не більше %{count} елементів" +msgstr[2] "повинен містити не більше %{count} елементів" + +## From Ecto.Changeset.validate_number/3 +msgid "must be less than %{number}" +msgstr "повинен мати значення менше ніж %{number}" + +msgid "must be greater than %{number}" +msgstr "повинен мати значення більше ніж %{number}" + +msgid "must be less than or equal to %{number}" +msgstr "повинен мати значення менше або рівне %{number}" + +msgid "must be greater than or equal to %{number}" +msgstr "повинен мати значення більше або рівне %{number}" + +msgid "must be equal to %{number}" +msgstr "повинен мати лише значення, рівне %{number}" + +#: lib/pleroma/web/common_api/common_api.ex:505 +#, elixir-format +msgid "Account not found" +msgstr "Обліковий запис не знайдено" + +#: lib/pleroma/web/common_api/common_api.ex:339 +#, elixir-format +msgid "Already voted" +msgstr "Вже проголосовано" + +#: lib/pleroma/web/oauth/oauth_controller.ex:359 +#, elixir-format +msgid "Bad request" +msgstr "Невірний запит" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:426 +#, elixir-format +msgid "Can't delete object" +msgstr "Виникла помилка при видаленні об'єкту" + +#: lib/pleroma/web/controller_helper.ex:105 +#: lib/pleroma/web/controller_helper.ex:111 +#, elixir-format +msgid "Can't display this activity" +msgstr "Не вдається відобразити цю активність" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:285 +#, elixir-format +msgid "Can't find user" +msgstr "Користувача не знайдено" + +#: lib/pleroma/web/pleroma_api/controllers/account_controller.ex:61 +#, elixir-format +msgid "Can't get favorites" +msgstr "Не вдається отримати вподобання" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:438 +#, elixir-format +msgid "Can't like object" +msgstr "Не вдається вподобати об’єкт" + +#: lib/pleroma/web/common_api/utils.ex:563 +#, elixir-format +msgid "Cannot post an empty status without attachments" +msgstr "Не вдається опублікувати порожнє повідомлення без вкладень" + +#: lib/pleroma/web/common_api/utils.ex:511 +#, elixir-format +msgid "Comment must be up to %{max_size} characters" +msgstr "Коментар може містити не більше %{max_size} символів" + +#: lib/pleroma/config/config_db.ex:191 +#, elixir-format +msgid "Config with params %{params} not found" +msgstr "Конфігурація з параметрами %{params} не знайдена" + +#: lib/pleroma/web/common_api/common_api.ex:181 +#: lib/pleroma/web/common_api/common_api.ex:185 +#, elixir-format +msgid "Could not delete" +msgstr "Не можу видалити" + +#: lib/pleroma/web/common_api/common_api.ex:231 +#, elixir-format +msgid "Could not favorite" +msgstr "Не вдалося додати до вподобаного" + +#: lib/pleroma/web/common_api/common_api.ex:453 +#, elixir-format +msgid "Could not pin" +msgstr "Не вдалося закріпити" + +#: lib/pleroma/web/common_api/common_api.ex:278 +#, elixir-format +msgid "Could not unfavorite" +msgstr "Не вдалося видалити з вподобаного" + +#: lib/pleroma/web/common_api/common_api.ex:463 +#, elixir-format +msgid "Could not unpin" +msgstr "Не вдалося відкріпити" + +#: lib/pleroma/web/common_api/common_api.ex:216 +#, elixir-format +msgid "Could not unrepeat" +msgstr "Не вдалося скасувати поширення" + +#: lib/pleroma/web/common_api/common_api.ex:512 +#: lib/pleroma/web/common_api/common_api.ex:521 +#, elixir-format +msgid "Could not update state" +msgstr "Не вдалося оновити стан" + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:207 +#, elixir-format +msgid "Error." +msgstr "Помилка." + +#: lib/pleroma/web/twitter_api/twitter_api.ex:106 +#, elixir-format +msgid "Invalid CAPTCHA" +msgstr "Невірна CAPTCHA" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:116 +#: lib/pleroma/web/oauth/oauth_controller.ex:568 +#, elixir-format +msgid "Invalid credentials" +msgstr "Неправильні дані автентифікації" + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 +#, elixir-format +msgid "Invalid credentials." +msgstr "Неправильні дані автентифікації." + +#: lib/pleroma/web/common_api/common_api.ex:355 +#, elixir-format +msgid "Invalid indices" +msgstr "Неправильні індекси" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid parameters" +msgstr "Неправильні параметри" + +#: lib/pleroma/web/common_api/utils.ex:414 +#, elixir-format +msgid "Invalid password." +msgstr "Неправильний пароль." + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:220 +#, elixir-format +msgid "Invalid request" +msgstr "Невірний запит" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:109 +#, elixir-format +msgid "Kocaptcha service unavailable" +msgstr "Сервіс Kocaptcha недоступний" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:112 +#, elixir-format +msgid "Missing parameters" +msgstr "Відсутні параметри" + +#: lib/pleroma/web/common_api/utils.ex:547 +#, elixir-format +msgid "No such conversation" +msgstr "Немає такої розмови" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 +#, elixir-format +msgid "No such permission_group" +msgstr "Не існує такої групи повноважень" + +#: lib/pleroma/plugs/uploaded_media.ex:84 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 +#: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 +#, elixir-format +msgid "Not found" +msgstr "Не знайдено" + +#: lib/pleroma/web/common_api/common_api.ex:331 +#, elixir-format +msgid "Poll's author can't vote" +msgstr "Автор опитування не може голосувати" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:50 lib/pleroma/web/mastodon_api/controllers/status_controller.ex:306 +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 +#, elixir-format +msgid "Record not found" +msgstr "Запис не знайдено" + +#: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 +#: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 +#, elixir-format +msgid "Something went wrong" +msgstr "Щось зламалося" + +#: lib/pleroma/web/common_api/activity_draft.ex:107 +#, elixir-format +msgid "The message visibility must be direct" +msgstr "Видимість у повідомлення повинна бути `Приватний`" + +#: lib/pleroma/web/common_api/utils.ex:573 +#, elixir-format +msgid "The status is over the character limit" +msgstr "Цей статус перевищує ліміт символів" + +#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 +#, elixir-format +msgid "This resource requires authentication." +msgstr "Цей ресурс вимагає автентифікації." + +#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 +#, elixir-format +msgid "Throttled" +msgstr "Обмежено. Перевищено ліміт запитів." + +#: lib/pleroma/web/common_api/common_api.ex:356 +#, elixir-format +msgid "Too many choices" +msgstr "Забагато варіантів вибору" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 +#, elixir-format +msgid "Unhandled activity type" +msgstr "Непідтримуваний тип активності" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 +#, elixir-format +msgid "You can't revoke your own admin status." +msgstr "Ви не можете позбавити самого себе статусу адміністратора." + +#: lib/pleroma/web/oauth/oauth_controller.ex:221 +#: lib/pleroma/web/oauth/oauth_controller.ex:308 +#, elixir-format +msgid "Your account is currently disabled" +msgstr "Ваш обліковий запис наразі вимкнено" + +#: lib/pleroma/web/oauth/oauth_controller.ex:183 +#: lib/pleroma/web/oauth/oauth_controller.ex:331 +#, elixir-format +msgid "Your login is missing a confirmed e-mail address" +msgstr "Ваша електрона адреса не підтверджена" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 +#, elixir-format +msgid "can't read inbox of %{nickname} as %{as_nickname}" +msgstr "" +"Не вдається прочитати \"Вхідні\" повідомлення %{nickname} як %{as_nickname}" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 +#, elixir-format +msgid "can't update outbox of %{nickname} as %{as_nickname}" +msgstr "" +"Не вдається оновити \"Вихідні\" повідомлення %{nickname} як %{as_nickname}" + +#: lib/pleroma/web/common_api/common_api.ex:471 +#, elixir-format +msgid "conversation is already muted" +msgstr "Розмова вже заглушена" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 +#, elixir-format +msgid "error" +msgstr "помилка" + +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 +#, elixir-format +msgid "mascots can only be images" +msgstr "талісманами можуть бути лише зображення" + +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 +#, elixir-format +msgid "not found" +msgstr "не знайдено" + +#: lib/pleroma/web/oauth/oauth_controller.ex:394 +#, elixir-format +msgid "Bad OAuth request." +msgstr "Невірний запит OAuth." + +#: lib/pleroma/web/twitter_api/twitter_api.ex:115 +#, elixir-format +msgid "CAPTCHA already used" +msgstr "CAPTCHA вже використана" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:112 +#, elixir-format +msgid "CAPTCHA expired" +msgstr "Термін дії CAPTCHA закінчився" + +#: lib/pleroma/plugs/uploaded_media.ex:57 +#, elixir-format +msgid "Failed" +msgstr "Не вдалося" + +#: lib/pleroma/web/oauth/oauth_controller.ex:410 +#, elixir-format +msgid "Failed to authenticate: %{message}." +msgstr "Помилка автентифікації: %{message}." + +#: lib/pleroma/web/oauth/oauth_controller.ex:441 +#, elixir-format +msgid "Failed to set up user account." +msgstr "Не вдалося створити обліковий запис." + +#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 +#, elixir-format +msgid "Insufficient permissions: %{permissions}." +msgstr "Недостатньо прав: %{permissions}." + +#: lib/pleroma/plugs/uploaded_media.ex:104 +#, elixir-format +msgid "Internal Error" +msgstr "Внутрішня помилка" + +#: lib/pleroma/web/oauth/fallback_controller.ex:22 +#: lib/pleroma/web/oauth/fallback_controller.ex:29 +#, elixir-format +msgid "Invalid Username/Password" +msgstr "Неправильне ім'я користувача або пароль" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:118 +#, elixir-format +msgid "Invalid answer data" +msgstr "Неправильна відповідь" + +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 +#, elixir-format +msgid "Nodeinfo schema version not handled" +msgstr "Версія схеми Nodeinfo не враховується" + +#: lib/pleroma/web/oauth/oauth_controller.ex:172 +#, elixir-format +msgid "This action is outside the authorized scopes" +msgstr "Ця дія виходить за рамки доступних повноважень" + +#: lib/pleroma/web/oauth/fallback_controller.ex:14 +#, elixir-format +msgid "Unknown error, please check the details and try again." +msgstr "Невідома помилка. Перевірте деталі та повторіть спробу." + +#: lib/pleroma/web/oauth/oauth_controller.ex:119 +#: lib/pleroma/web/oauth/oauth_controller.ex:158 +#, elixir-format +msgid "Unlisted redirect_uri." +msgstr "Невідомий redirect_uri." + +#: lib/pleroma/web/oauth/oauth_controller.ex:390 +#, elixir-format +msgid "Unsupported OAuth provider: %{provider}." +msgstr "Непідтримуваний постачальник послуг OAuth: %{provider}." + +#: lib/pleroma/uploaders/uploader.ex:72 +#, elixir-format +msgid "Uploader callback timeout" +msgstr "Тайм-аут при завантаженні" + +#: lib/pleroma/web/uploader_controller.ex:23 +#, elixir-format +msgid "bad request" +msgstr "невірний запит" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:103 +#, elixir-format +msgid "CAPTCHA Error" +msgstr "Помилка CAPTCHA" + +#: lib/pleroma/web/common_api/common_api.ex:290 +#, elixir-format +msgid "Could not add reaction emoji" +msgstr "Не вдалося додати емодзі для реакції" + +#: lib/pleroma/web/common_api/common_api.ex:301 +#, elixir-format +msgid "Could not remove reaction emoji" +msgstr "Не вдалося видалити реакцію" + +#: lib/pleroma/web/twitter_api/twitter_api.ex:129 +#, elixir-format +msgid "Invalid CAPTCHA (Missing parameter: %{name})" +msgstr "Недійсна CAPTCHA (Відсутній параметр: %{name})" + +#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 +#, elixir-format +msgid "List not found" +msgstr "Список не знайдено" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 +#, elixir-format +msgid "Missing parameter: %{name}" +msgstr "Відсутній параметр: %{name}" + +#: lib/pleroma/web/oauth/oauth_controller.ex:210 +#: lib/pleroma/web/oauth/oauth_controller.ex:321 +#, elixir-format +msgid "Password reset is required" +msgstr "Потрібно скинути пароль" + +#: lib/pleroma/tests/auth_test_controller.ex:9 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/config_controller.ex:6 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/invite_controller.ex:6 lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex:6 lib/pleroma/web/admin_api/controllers/relay_controller.ex:6 +#: lib/pleroma/web/admin_api/controllers/report_controller.ex:6 lib/pleroma/web/admin_api/controllers/status_controller.ex:6 +#: lib/pleroma/web/controller_helper.ex:6 lib/pleroma/web/embed_controller.ex:6 +#: lib/pleroma/web/fallback_redirect_controller.ex:6 lib/pleroma/web/feed/tag_controller.ex:6 +#: lib/pleroma/web/feed/user_controller.ex:6 lib/pleroma/web/mailer/subscription_controller.ex:2 +#: lib/pleroma/web/masto_fe_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/app_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/auth_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/filter_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/instance_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/list_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/marker_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex:14 +#: lib/pleroma/web/mastodon_api/controllers/media_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/report_controller.ex:8 +#: lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/search_controller.ex:6 +#: lib/pleroma/web/mastodon_api/controllers/status_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:7 +#: lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex:6 lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:6 +#: lib/pleroma/web/media_proxy/media_proxy_controller.ex:6 lib/pleroma/web/mongooseim/mongoose_im_controller.ex:6 +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:6 lib/pleroma/web/oauth/fallback_controller.ex:6 +#: lib/pleroma/web/oauth/mfa_controller.ex:10 lib/pleroma/web/oauth/oauth_controller.ex:6 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/account_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/chat_controller.ex:5 lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:2 lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:6 lib/pleroma/web/pleroma_api/controllers/notification_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex:6 +#: lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex:7 lib/pleroma/web/static_fe/static_fe_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/password_controller.ex:10 lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex:6 +#: lib/pleroma/web/twitter_api/controllers/util_controller.ex:6 lib/pleroma/web/twitter_api/twitter_api_controller.ex:6 +#: lib/pleroma/web/uploader_controller.ex:6 lib/pleroma/web/web_finger/web_finger_controller.ex:6 +#, elixir-format +msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." +msgstr "" +"Порушення безпеки: перевірка обсягу OAuth не була оброблена, ні явно " +"пропущена." + +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 +#, elixir-format +msgid "Two-factor authentication enabled, you must use a access token." +msgstr "" +"Двофакторна автентифікація ввімкнена, ви повинні використовувати ключ " +"доступу." + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 +#, elixir-format +msgid "Unexpected error occurred while adding file to pack." +msgstr "Несподівана помилка при додаванні файлу в пакет." + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 +#, elixir-format +msgid "Unexpected error occurred while creating pack." +msgstr "Несподівана помилка під час створення пакета." + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 +#, elixir-format +msgid "Unexpected error occurred while removing file from pack." +msgstr "Під час видалення файлу з пакета сталася несподівана помилка." + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 +#, elixir-format +msgid "Unexpected error occurred while updating file in pack." +msgstr "Під час оновлення файлу в пакеті сталася несподівана помилка." + +#: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 +#, elixir-format +msgid "Unexpected error occurred while updating pack metadata." +msgstr "Під час оновлення метаданих пакета сталася несподівана помилка." + +#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 +#, elixir-format +msgid "Web push subscription is disabled on this Pleroma instance" +msgstr "Web push-сповіщення вимкнені на цьому інстансі Pleroma" + +#: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 +#, elixir-format +msgid "You can't revoke your own admin/moderator status." +msgstr "Ви не можете позбавити самого себе статусу адміністратора/модератора." + +#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 +#, elixir-format +msgid "authorization required for timeline view" +msgstr "необхідно ввійти в систему для перегляду стрічки повідомлень" + +#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 +#, elixir-format +msgid "Access denied" +msgstr "Доступ заборонено" + +#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 +#, elixir-format +msgid "This API requires an authenticated user" +msgstr "Цей API вимагає автентифікованого користувача" + +#: lib/pleroma/plugs/user_is_admin_plug.ex:21 +#, elixir-format +msgid "User is not an admin." +msgstr "Користувач не є адміністратором." diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po index 8b24d4a86..ecf1dab6b 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-09-20 13:18+0000\n" -"PO-Revision-Date: 2020-10-22 18:25+0000\n" +"PO-Revision-Date: 2020-12-14 06:00+0000\n" "Last-Translator: shironeko <shironeko@tesaguri.club>\n" "Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" "projects/pleroma/pleroma/zh_Hans/>\n" @@ -146,9 +146,9 @@ msgid "Cannot post an empty status without attachments" msgstr "无法发送空白且不包含附件的状态" #: lib/pleroma/web/common_api/utils.ex:511 -#, elixir-format +#, elixir-format, fuzzy msgid "Comment must be up to %{max_size} characters" -msgstr "" +msgstr "评论最多可使用 %{max_size} 字符" #: lib/pleroma/config/config_db.ex:191 #, elixir-format @@ -250,21 +250,21 @@ msgstr "没有该对话" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:388 #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:414 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:456 -#, elixir-format +#, elixir-format, fuzzy msgid "No such permission_group" -msgstr "" +msgstr "没有该权限组" #: lib/pleroma/plugs/uploaded_media.ex:84 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:486 lib/pleroma/web/admin_api/controllers/fallback_controller.ex:11 #: lib/pleroma/web/feed/user_controller.ex:71 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #, elixir-format msgid "Not found" -msgstr "" +msgstr "未找到" #: lib/pleroma/web/common_api/common_api.ex:331 #, elixir-format msgid "Poll's author can't vote" -msgstr "" +msgstr "投票的发起者不能投票" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 @@ -272,39 +272,39 @@ msgstr "" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #, elixir-format msgid "Record not found" -msgstr "" +msgstr "未找到该记录" #: lib/pleroma/web/admin_api/controllers/fallback_controller.ex:35 #: lib/pleroma/web/feed/user_controller.ex:77 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:36 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #, elixir-format msgid "Something went wrong" -msgstr "" +msgstr "发生了一些错误" #: lib/pleroma/web/common_api/activity_draft.ex:107 #, elixir-format msgid "The message visibility must be direct" -msgstr "" +msgstr "该消息必须为私信" #: lib/pleroma/web/common_api/utils.ex:573 #, elixir-format msgid "The status is over the character limit" -msgstr "" +msgstr "状态超过了字符数限制" #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #, elixir-format msgid "This resource requires authentication." -msgstr "" +msgstr "该资源需要认证。" #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 -#, elixir-format +#, elixir-format, fuzzy msgid "Throttled" -msgstr "" +msgstr "节流了" #: lib/pleroma/web/common_api/common_api.ex:356 #, elixir-format msgid "Too many choices" -msgstr "" +msgstr "太多选项" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:443 #, elixir-format @@ -314,101 +314,101 @@ msgstr "" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:485 #, elixir-format msgid "You can't revoke your own admin status." -msgstr "" +msgstr "您不能撤消自己的管理员权限。" #: lib/pleroma/web/oauth/oauth_controller.ex:221 #: lib/pleroma/web/oauth/oauth_controller.ex:308 #, elixir-format msgid "Your account is currently disabled" -msgstr "" +msgstr "您的账户已被禁用" #: lib/pleroma/web/oauth/oauth_controller.ex:183 #: lib/pleroma/web/oauth/oauth_controller.ex:331 #, elixir-format msgid "Your login is missing a confirmed e-mail address" -msgstr "" +msgstr "您的账户缺少已认证的 e-mail 地址" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:390 #, elixir-format msgid "can't read inbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "无法以 %{as_nickname} 读取 %{nickname} 的收件箱" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:473 #, elixir-format msgid "can't update outbox of %{nickname} as %{as_nickname}" -msgstr "" +msgstr "无法以 %{as_nickname} 更新 %{nickname} 的出件箱" #: lib/pleroma/web/common_api/common_api.ex:471 #, elixir-format msgid "conversation is already muted" -msgstr "" +msgstr "对话已经被静音" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:314 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:492 #, elixir-format msgid "error" -msgstr "" +msgstr "错误" #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:32 #, elixir-format msgid "mascots can only be images" -msgstr "" +msgstr "吉祥物只能是图片" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:62 #, elixir-format msgid "not found" -msgstr "" +msgstr "未找到" #: lib/pleroma/web/oauth/oauth_controller.ex:394 #, elixir-format msgid "Bad OAuth request." -msgstr "" +msgstr "错误的 OAuth 请求。" #: lib/pleroma/web/twitter_api/twitter_api.ex:115 #, elixir-format msgid "CAPTCHA already used" -msgstr "" +msgstr "验证码已被使用" #: lib/pleroma/web/twitter_api/twitter_api.ex:112 #, elixir-format msgid "CAPTCHA expired" -msgstr "" +msgstr "验证码已过期" #: lib/pleroma/plugs/uploaded_media.ex:57 #, elixir-format msgid "Failed" -msgstr "" +msgstr "失败" #: lib/pleroma/web/oauth/oauth_controller.ex:410 -#, elixir-format +#, elixir-format, fuzzy msgid "Failed to authenticate: %{message}." -msgstr "" +msgstr "认证失败:%{message}。" #: lib/pleroma/web/oauth/oauth_controller.ex:441 #, elixir-format msgid "Failed to set up user account." -msgstr "" +msgstr "建立用户帐号失败。" #: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #, elixir-format msgid "Insufficient permissions: %{permissions}." -msgstr "" +msgstr "权限不足:%{permissions}。" #: lib/pleroma/plugs/uploaded_media.ex:104 #, elixir-format msgid "Internal Error" -msgstr "" +msgstr "内部错误" #: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:29 #, elixir-format msgid "Invalid Username/Password" -msgstr "" +msgstr "无效的用户名/密码" #: lib/pleroma/web/twitter_api/twitter_api.ex:118 -#, elixir-format +#, elixir-format, fuzzy msgid "Invalid answer data" -msgstr "" +msgstr "无效的回答数据" #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 #, elixir-format @@ -418,12 +418,12 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:172 #, elixir-format msgid "This action is outside the authorized scopes" -msgstr "" +msgstr "此操作在许可范围以外" #: lib/pleroma/web/oauth/fallback_controller.ex:14 #, elixir-format msgid "Unknown error, please check the details and try again." -msgstr "" +msgstr "未知错误,请检查并重试。" #: lib/pleroma/web/oauth/oauth_controller.ex:119 #: lib/pleroma/web/oauth/oauth_controller.ex:158 @@ -434,53 +434,53 @@ msgstr "" #: lib/pleroma/web/oauth/oauth_controller.ex:390 #, elixir-format msgid "Unsupported OAuth provider: %{provider}." -msgstr "" +msgstr "不支持的 OAuth 提供者:%{provider}。" #: lib/pleroma/uploaders/uploader.ex:72 -#, elixir-format +#, elixir-format, fuzzy msgid "Uploader callback timeout" -msgstr "" +msgstr "上传回复超时" #: lib/pleroma/web/uploader_controller.ex:23 #, elixir-format msgid "bad request" -msgstr "" +msgstr "错误的请求" #: lib/pleroma/web/twitter_api/twitter_api.ex:103 #, elixir-format msgid "CAPTCHA Error" -msgstr "" +msgstr "验证码错误" #: lib/pleroma/web/common_api/common_api.ex:290 -#, elixir-format +#, elixir-format, fuzzy msgid "Could not add reaction emoji" -msgstr "" +msgstr "无法添加表情反应" #: lib/pleroma/web/common_api/common_api.ex:301 #, elixir-format msgid "Could not remove reaction emoji" -msgstr "" +msgstr "无法移除表情反应" #: lib/pleroma/web/twitter_api/twitter_api.ex:129 #, elixir-format msgid "Invalid CAPTCHA (Missing parameter: %{name})" -msgstr "" +msgstr "无效的验证码(缺少参数:%{name})" #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #, elixir-format msgid "List not found" -msgstr "" +msgstr "未找到列表" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:123 #, elixir-format msgid "Missing parameter: %{name}" -msgstr "" +msgstr "缺少参数:%{name}" #: lib/pleroma/web/oauth/oauth_controller.ex:210 #: lib/pleroma/web/oauth/oauth_controller.ex:321 #, elixir-format msgid "Password reset is required" -msgstr "" +msgstr "需要重置密码" #: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:6 @@ -520,61 +520,61 @@ msgid "Security violation: OAuth scopes check was neither handled nor explicitly msgstr "" #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 -#, elixir-format +#, elixir-format, fuzzy msgid "Two-factor authentication enabled, you must use a access token." -msgstr "" +msgstr "已启用两因素验证,您需要使用访问令牌。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:210 #, elixir-format msgid "Unexpected error occurred while adding file to pack." -msgstr "" +msgstr "向表情包添加文件时发生了没有预料到的错误。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:138 #, elixir-format msgid "Unexpected error occurred while creating pack." -msgstr "" +msgstr "创建表情包时发生了没有预料到的错误。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:278 #, elixir-format msgid "Unexpected error occurred while removing file from pack." -msgstr "" +msgstr "从表情包移除文件时发生了没有预料到的错误。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:250 #, elixir-format msgid "Unexpected error occurred while updating file in pack." -msgstr "" +msgstr "更新表情包内的文件时发生了没有预料到的错误。" #: lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex:179 #, elixir-format msgid "Unexpected error occurred while updating pack metadata." -msgstr "" +msgstr "更新表情包元数据时发生了没有预料到的错误。" #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 -#, elixir-format +#, elixir-format, fuzzy msgid "Web push subscription is disabled on this Pleroma instance" -msgstr "" +msgstr "此 Pleroma 实例禁用了网页推送订阅" #: lib/pleroma/web/admin_api/controllers/admin_api_controller.ex:451 #, elixir-format msgid "You can't revoke your own admin/moderator status." -msgstr "" +msgstr "您不能撤消自己的管理员权限。" #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:126 #, elixir-format msgid "authorization required for timeline view" -msgstr "" +msgstr "浏览时间线需要认证" #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:24 #, elixir-format msgid "Access denied" -msgstr "" +msgstr "拒绝访问" #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:282 #, elixir-format msgid "This API requires an authenticated user" -msgstr "" +msgstr "此 API 需要已认证的用户" #: lib/pleroma/plugs/user_is_admin_plug.ex:21 #, elixir-format msgid "User is not an admin." -msgstr "" +msgstr "该用户不是管理员。" diff --git a/priv/repo/migrations/20201217172858_data_migration_prolong_o_auth_tokens_valid_until.exs b/priv/repo/migrations/20201217172858_data_migration_prolong_o_auth_tokens_valid_until.exs new file mode 100644 index 000000000..560cc7447 --- /dev/null +++ b/priv/repo/migrations/20201217172858_data_migration_prolong_o_auth_tokens_valid_until.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.DataMigrationProlongOAuthTokensValidUntil do + use Ecto.Migration + + def up do + expires_in = Pleroma.Config.get!([:oauth2, :token_expires_in]) + valid_until = NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in, :second) + execute("update oauth_tokens set valid_until = '#{valid_until}'") + end + + def down do + :noop + end +end diff --git a/priv/static/images/logo.png b/priv/static/images/logo.png Binary files differnew file mode 100644 index 000000000..7744b1acc --- /dev/null +++ b/priv/static/images/logo.png diff --git a/priv/static/instance/static.css b/priv/static/instance/static.css new file mode 100644 index 000000000..487e1ec27 --- /dev/null +++ b/priv/static/instance/static.css @@ -0,0 +1,296 @@ +* { + box-sizing: border-box; +} + +:root { + --brand-color: #d8a070; + --background-color: #121a24; + --foreground-color: #182230; + --primary-text-color: #b9b9ba; + --muted-text-color: #89898a; +} + +body { + background-color: var(--background-color); + font-family: sans-serif; + color: var(--primary-text-color); + padding: 0; + margin: 0; +} + +.instance-header { + height: 60px; + padding: 10px; + background: var(--foreground-color); + box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5); +} + +.instance-header__content { + display: flex; + align-items: center; + max-width: 400px; + margin: 0 auto; +} + +.instance-header__thumbnail { + max-width: 40px; + border-radius: 4px; + margin-right: 12px; +} + +.instance-header__title { + font-size: 16px; + font-weight: bold; + color: var(--primary-text-color); +} + +.container { + max-width: 400px; + background-color: var(--foreground-color); + border-radius: 4px; + overflow: hidden; + margin: 35px auto; + box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5); +} + +.container__content { + padding: 0 20px; +} + +h1 { + margin: 0; + font-size: 24px; + text-align: center; +} + +h2 { + color: var(--primary-text-color); + font-weight: normal; + font-size: 18px; + margin-bottom: 20px; +} + +a { + color: var(--brand-color); + text-decoration: none; +} + +form { + width: 100%; +} + +.input { + color: var(--muted-text-color); + display: flex; + flex-direction: column; +} + +input { + box-sizing: content-box; + padding: 10px; + margin-top: 5px; + margin-bottom: 10px; + background-color: var(--background-color); + color: var(--primary-text-color); + border: 0; + transition-property: border-bottom; + transition-duration: 0.35s; + border-bottom: 2px solid #2a384a; + font-size: 14px; +} + +.scopes-input { + display: flex; + flex-direction: column; + margin: 1em 0; + color: var(--muted-text-color); +} + +.scopes-input label:first-child { + height: 2em; +} + +.scopes { + display: flex; + flex-wrap: wrap; + color: var(--primary-text-color); +} + +.scope { + display: flex; + flex-basis: 100%; + height: 2em; + align-items: center; +} + +.scope:before { + color: var(--primary-text-color); + content: "✔\fe0e"; + margin-left: 1em; + margin-right: 1em; +} + +[type="checkbox"] + label { + display: none; + cursor: pointer; + margin: 0.5em; +} + +[type="checkbox"] { + display: none; +} + +[type="checkbox"] + label:before { + cursor: pointer; + display: inline-block; + color: white; + background-color: var(--background-color); + border: 4px solid var(--background-color); + box-shadow: 0px 0px 1px 0 var(--brand-color); + width: 1.2em; + height: 1.2em; + margin-right: 1.0em; + content: ""; + transition-property: background-color; + transition-duration: 0.35s; + color: var(--background-color); + margin-bottom: -0.2em; + border-radius: 2px; +} + +[type="checkbox"]:checked + label:before { + background-color: var(--brand-color); +} + +input:focus { + outline: none; + border-bottom: 2px solid var(--brand-color); +} + +.actions { + display: flex; + justify-content: flex-end; +} + +.actions button, +.actions a.button { + width: auto; + margin-left: 10px; +} + +a.button, +button { + width: 100%; + background-color: #1c2a3a; + color: var(--primary-text-color); + border-radius: 4px; + border: none; + padding: 10px 16px; + margin-top: 20px; + margin-bottom: 20px; + text-transform: uppercase; + font-size: 16px; + box-shadow: 0px 0px 2px 0px black, + 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, + 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; +} + +a.button:hover, +button:hover { + cursor: pointer; + box-shadow: 0px 0px 0px 1px var(--brand-color), + 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, + 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; +} + +.alert-danger { + width: 100%; + background-color: #931014; + border: 1px solid #a06060; + border-radius: 4px; + padding: 10px; + margin-top: 20px; + font-weight: 500; + font-size: 16px; +} + +.alert-info { + width: 100%; + border-radius: 4px; + border: 1px solid #7d796a; + padding: 10px; + margin-top: 20px; + font-weight: 500; + font-size: 16px; +} + +.account-header__banner { + width: 100%; + height: 112px; + background-size: cover; + background-position: center; +} + +.account-header__avatar { + width: 94px; + height: 94px; + background-size: cover; + background-position: center; + margin: -47px 10px 0; + border: 6px solid var(--foreground-color); + border-radius: 999px; +} + +.account-header__meta { + padding: 6px 20px 17px; +} + +.account-header__display-name { + font-size: 20px; + font-weight: bold; +} + +.account-header__nickname { + font-size: 14px; + color: var(--muted-text-color); +} + +@media all and (max-width: 420px) { + .container { + margin: 0 auto; + border-radius: 0; + } + + .scope { + flex-basis: 0%; + } + + .scope:before { + content: ""; + margin-left: 0em; + margin-right: 1em; + } + + .scope:first-child:before { + margin-left: 1em; + content: "✔\fe0e"; + } + + .scope:after { + content: ","; + } + + .scope:last-child:after { + content: ""; + } +} +.form-row { + display: flex; +} +.form-row > label { + line-height: 47px; + flex: 1; +} +.form-row > input { + flex: 2; +} diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index 292a5ef5f..cf28576b5 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -73,7 +73,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do describe "running update_users_following_followers_counts" do test "following and followers count are updated" do [user, user2] = insert_pair(:user) - {:ok, %User{} = user} = User.follow(user, user2) + {:ok, %User{} = user, _user2} = User.follow(user, user2) following = User.following(user) @@ -87,7 +87,8 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do assert user.follower_count == 3 - assert :ok == Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"]) + assert {:ok, :ok} == + Mix.Tasks.Pleroma.Database.run(["update_users_following_followers_counts"]) user = User.get_by_id(user.id) diff --git a/test/mix/tasks/pleroma/user_test.exs b/test/mix/tasks/pleroma/user_test.exs index ae0c50443..de8ab27e5 100644 --- a/test/mix/tasks/pleroma/user_test.exs +++ b/test/mix/tasks/pleroma/user_test.exs @@ -462,24 +462,24 @@ defmodule Mix.Tasks.Pleroma.UserTest do end end - describe "running toggle_confirmed" do + describe "running confirm" do test "user is confirmed" do %{id: id, nickname: nickname} = insert(:user, confirmation_pending: false) - assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname]) + assert :ok = Mix.Tasks.Pleroma.User.run(["confirm", nickname]) assert_received {:mix_shell, :info, [message]} - assert message == "#{nickname} needs confirmation." + assert message == "#{nickname} doesn't need confirmation." user = Repo.get(User, id) - assert user.confirmation_pending - assert user.confirmation_token + refute user.confirmation_pending + refute user.confirmation_token end test "user is not confirmed" do %{id: id, nickname: nickname} = insert(:user, confirmation_pending: true, confirmation_token: "some token") - assert :ok = Mix.Tasks.Pleroma.User.run(["toggle_confirmed", nickname]) + assert :ok = Mix.Tasks.Pleroma.User.run(["confirm", nickname]) assert_received {:mix_shell, :info, [message]} assert message == "#{nickname} doesn't need confirmation." @@ -489,7 +489,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do end test "it prints an error message when user is not exist" do - Mix.Tasks.Pleroma.User.run(["toggle_confirmed", "foo"]) + Mix.Tasks.Pleroma.User.run(["confirm", "foo"]) assert_received {:mix_shell, :error, [message]} assert message =~ "No local user" @@ -503,7 +503,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do moot = insert(:user, nickname: "moot") kawen = insert(:user, nickname: "kawen", name: "fediverse expert moon") - {:ok, user} = User.follow(user, moon) + {:ok, user, moon} = User.follow(user, moon) assert [moon.id, kawen.id] == User.Search.search("moon") |> Enum.map(& &1.id) diff --git a/test/pleroma/bbs/handler_test.exs b/test/pleroma/bbs/handler_test.exs index eb716486e..e605c2726 100644 --- a/test/pleroma/bbs/handler_test.exs +++ b/test/pleroma/bbs/handler_test.exs @@ -19,7 +19,7 @@ defmodule Pleroma.BBS.HandlerTest do user = insert(:user) followed = insert(:user) - {:ok, user} = User.follow(user, followed) + {:ok, user, followed} = User.follow(user, followed) {:ok, _first} = CommonAPI.post(user, %{status: "hey"}) {:ok, _second} = CommonAPI.post(followed, %{status: "hello"}) diff --git a/test/pleroma/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs index a75623bb4..a214e59a7 100644 --- a/test/pleroma/emails/user_email_test.exs +++ b/test/pleroma/emails/user_email_test.exs @@ -45,4 +45,15 @@ defmodule Pleroma.Emails.UserEmailTest do assert email.html_body =~ Router.Helpers.confirm_email_url(Endpoint, :confirm_email, user.id, "conf-token") end + + test "build approval pending email" do + config = Pleroma.Config.get(:instance) + user = insert(:user) + email = UserEmail.approval_pending_email(user) + + assert email.from == {config[:name], config[:notify_email]} + assert email.to == [{user.name, user.email}] + assert email.subject == "Your account is awaiting approval" + assert email.html_body =~ "Awaiting Approval" + end end diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index ed2cd219d..a6558f995 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -779,7 +779,7 @@ defmodule Pleroma.NotificationTest do other_user = insert(:user) {:ok, other_user} = User.block_domain(other_user, blocked_domain) - {:ok, other_user} = User.follow(other_user, user) + {:ok, other_user, user} = User.follow(other_user, user) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"}) @@ -1070,7 +1070,7 @@ defmodule Pleroma.NotificationTest do blocked = insert(:user, ap_id: "http://some-domain.com") {:ok, user} = User.block_domain(user, "some-domain.com") - {:ok, _} = User.follow(user, blocked) + {:ok, _, _} = User.follow(user, blocked) {:ok, _activity} = CommonAPI.post(blocked, %{status: "hey @#{user.nickname}"}) diff --git a/test/pleroma/user/import_test.exs b/test/pleroma/user/import_test.exs index e404deeb5..e198cdc08 100644 --- a/test/pleroma/user/import_test.exs +++ b/test/pleroma/user/import_test.exs @@ -30,7 +30,7 @@ defmodule Pleroma.User.ImportTest do assert {:ok, result} = ObanHelpers.perform(job) assert is_list(result) - assert result == [user2, user3] + assert result == [refresh_record(user2), refresh_record(user3)] assert User.following?(user1, user2) assert User.following?(user1, user3) end diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs index de1df2e9c..accb0b816 100644 --- a/test/pleroma/user_search_test.exs +++ b/test/pleroma/user_search_test.exs @@ -151,8 +151,8 @@ defmodule Pleroma.UserSearchTest do follower = insert(:user, %{name: "Doe"}) friend = insert(:user, %{name: "Doe"}) - {:ok, follower} = User.follow(follower, u1) - {:ok, u1} = User.follow(u1, friend) + {:ok, follower, u1} = User.follow(follower, u1) + {:ok, u1, friend} = User.follow(u1, friend) assert [friend.id, follower.id, u2.id] -- Enum.map(User.search("doe", resolve: false, for_user: u1), & &1.id) == [] @@ -165,9 +165,9 @@ defmodule Pleroma.UserSearchTest do following_jimi = insert(:user, %{name: "Lizz Wright"}) follower_lizz = insert(:user, %{name: "Jimi"}) - {:ok, lizz} = User.follow(lizz, following_lizz) - {:ok, _jimi} = User.follow(jimi, following_jimi) - {:ok, _follower_lizz} = User.follow(follower_lizz, lizz) + {:ok, lizz, following_lizz} = User.follow(lizz, following_lizz) + {:ok, _jimi, _following_jimi} = User.follow(jimi, following_jimi) + {:ok, _follower_lizz, _lizz} = User.follow(follower_lizz, lizz) assert Enum.map(User.search("jimi", following: true, for_user: lizz), & &1.id) == [ following_lizz.id diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index e01a940cb..40bbcad0b 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -233,7 +233,7 @@ defmodule Pleroma.UserTest do {:ok, _user_relationship} = User.block(user, blocked) {:ok, _user_relationship} = User.block(reverse_blocked, user) - {:ok, user} = User.follow(user, followed_zero) + {:ok, user, followed_zero} = User.follow(user, followed_zero) {:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked]) @@ -262,7 +262,7 @@ defmodule Pleroma.UserTest do user = insert(:user) followed = insert(:user) - {:ok, user} = User.follow(user, followed) + {:ok, user, followed} = User.follow(user, followed) user = User.get_cached_by_id(user.id) followed = User.get_cached_by_ap_id(followed.ap_id) @@ -302,7 +302,7 @@ defmodule Pleroma.UserTest do follower = insert(:user, is_locked: true) followed = insert(:user, is_locked: true) - {:ok, follower} = User.maybe_direct_follow(follower, followed) + {:ok, follower, followed} = User.maybe_direct_follow(follower, followed) refute User.following?(follower, followed) end @@ -330,7 +330,7 @@ defmodule Pleroma.UserTest do following_address: "http://localhost:4001/users/fuser2/following" }) - {:ok, user} = User.follow(user, followed, :follow_accept) + {:ok, user, followed} = User.follow(user, followed, :follow_accept) {:ok, user, _activity} = User.unfollow(user, followed) @@ -343,7 +343,7 @@ defmodule Pleroma.UserTest do followed = insert(:user) user = insert(:user) - {:ok, user} = User.follow(user, followed, :follow_accept) + {:ok, user, followed} = User.follow(user, followed, :follow_accept) assert User.following(user) == [user.follower_address, followed.follower_address] @@ -535,6 +535,22 @@ defmodule Pleroma.UserTest do |> assert_email_sent() end + test "sends a pending approval email" do + clear_config([:instance, :account_approval_required], true) + + {:ok, user} = + User.register_changeset(%User{}, @full_user_data) + |> User.register() + + ObanHelpers.perform_all() + + assert_email_sent( + from: Pleroma.Config.Helpers.sender(), + to: {user.name, user.email}, + subject: "Your account is awaiting approval" + ) + end + test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do Pleroma.Config.put([:instance, :account_activation_required], true) @@ -911,8 +927,8 @@ defmodule Pleroma.UserTest do follower_two = insert(:user) not_follower = insert(:user) - {:ok, follower_one} = User.follow(follower_one, user) - {:ok, follower_two} = User.follow(follower_two, user) + {:ok, follower_one, user} = User.follow(follower_one, user) + {:ok, follower_two, user} = User.follow(follower_two, user) res = User.get_followers(user) @@ -927,8 +943,8 @@ defmodule Pleroma.UserTest do followed_two = insert(:user) not_followed = insert(:user) - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) + {:ok, user, followed_one} = User.follow(user, followed_one) + {:ok, user, followed_two} = User.follow(user, followed_two) res = User.get_friends(user) @@ -1098,8 +1114,8 @@ defmodule Pleroma.UserTest do blocker = insert(:user) blocked = insert(:user) - {:ok, blocker} = User.follow(blocker, blocked) - {:ok, blocked} = User.follow(blocked, blocker) + {:ok, blocker, blocked} = User.follow(blocker, blocked) + {:ok, blocked, blocker} = User.follow(blocked, blocker) assert User.following?(blocker, blocked) assert User.following?(blocked, blocker) @@ -1117,7 +1133,7 @@ defmodule Pleroma.UserTest do blocker = insert(:user) blocked = insert(:user) - {:ok, blocker} = User.follow(blocker, blocked) + {:ok, blocker, blocked} = User.follow(blocker, blocked) assert User.following?(blocker, blocked) refute User.following?(blocked, blocker) @@ -1135,7 +1151,7 @@ defmodule Pleroma.UserTest do blocker = insert(:user) blocked = insert(:user) - {:ok, blocked} = User.follow(blocked, blocker) + {:ok, blocked, blocker} = User.follow(blocked, blocker) refute User.following?(blocker, blocked) assert User.following?(blocked, blocker) @@ -1233,7 +1249,7 @@ defmodule Pleroma.UserTest do good_eggo = insert(:user, %{ap_id: "https://meanies.social/user/cuteposter"}) {:ok, user} = User.block_domain(user, "meanies.social") - {:ok, user} = User.follow(user, good_eggo) + {:ok, user, good_eggo} = User.follow(user, good_eggo) refute User.blocks?(user, good_eggo) end @@ -1267,8 +1283,8 @@ defmodule Pleroma.UserTest do assert Enum.map([actor, addressed], & &1.ap_id) -- Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == [] - {:ok, user} = User.follow(user, actor) - {:ok, _user_two} = User.follow(user_two, actor) + {:ok, user, actor} = User.follow(user, actor) + {:ok, _user_two, _actor} = User.follow(user_two, actor) recipients = User.get_recipients_from_activity(activity) assert length(recipients) == 3 assert user in recipients @@ -1289,8 +1305,8 @@ defmodule Pleroma.UserTest do assert Enum.map([actor, addressed], & &1.ap_id) -- Enum.map(User.get_recipients_from_activity(activity), & &1.ap_id) == [] - {:ok, _actor} = User.follow(actor, user) - {:ok, _actor} = User.follow(actor, user_two) + {:ok, _actor, _user} = User.follow(actor, user) + {:ok, _actor, _user_two} = User.follow(actor, user_two) recipients = User.get_recipients_from_activity(activity) assert length(recipients) == 2 assert addressed in recipients @@ -1311,7 +1327,7 @@ defmodule Pleroma.UserTest do user = insert(:user) user2 = insert(:user) - {:ok, user} = User.follow(user, user2) + {:ok, user, user2} = User.follow(user, user2) {:ok, _user} = User.deactivate(user) user2 = User.get_cached_by_id(user2.id) @@ -1324,7 +1340,7 @@ defmodule Pleroma.UserTest do user = insert(:user) user2 = insert(:user) - {:ok, user2} = User.follow(user2, user) + {:ok, user2, user} = User.follow(user2, user) assert user2.following_count == 1 assert User.following_count(user2) == 1 @@ -1342,7 +1358,7 @@ defmodule Pleroma.UserTest do user = insert(:user) user2 = insert(:user) - {:ok, user2} = User.follow(user2, user) + {:ok, user2, user} = User.follow(user2, user) {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{user2.nickname}"}) @@ -1393,6 +1409,98 @@ defmodule Pleroma.UserTest do assert false == user.approval_pending end) end + + test "it sends welcome email if it is set" do + clear_config([:welcome, :email, :enabled], true) + clear_config([:welcome, :email, :sender], "tester@test.me") + + user = insert(:user, approval_pending: true) + welcome_user = insert(:user, email: "tester@test.me") + instance_name = Pleroma.Config.get([:instance, :name]) + + User.approve(user) + + ObanHelpers.perform_all() + + assert_email_sent( + from: {instance_name, welcome_user.email}, + to: {user.name, user.email}, + html_body: "Welcome to #{instance_name}" + ) + end + + test "approving an approved user does not trigger post-register actions" do + clear_config([:welcome, :email, :enabled], true) + + user = insert(:user, approval_pending: false) + User.approve(user) + + ObanHelpers.perform_all() + + assert_no_email_sent() + end + end + + describe "confirm" do + test "confirms a user" do + user = insert(:user, confirmation_pending: true) + assert true == user.confirmation_pending + {:ok, user} = User.confirm(user) + assert false == user.confirmation_pending + end + + test "confirms a list of users" do + unconfirmed_users = [ + insert(:user, confirmation_pending: true), + insert(:user, confirmation_pending: true), + insert(:user, confirmation_pending: true) + ] + + {:ok, users} = User.confirm(unconfirmed_users) + + assert Enum.count(users) == 3 + + Enum.each(users, fn user -> + assert false == user.confirmation_pending + end) + end + + test "sends approval emails when `approval_pending: true`" do + admin = insert(:user, is_admin: true) + user = insert(:user, confirmation_pending: true, approval_pending: true) + User.confirm(user) + + ObanHelpers.perform_all() + + user_email = Pleroma.Emails.UserEmail.approval_pending_email(user) + admin_email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user) + + notify_email = Pleroma.Config.get([:instance, :notify_email]) + instance_name = Pleroma.Config.get([:instance, :name]) + + # User approval email + assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: user_email.html_body + ) + + # Admin email + assert_email_sent( + from: {instance_name, notify_email}, + to: {admin.name, admin.email}, + html_body: admin_email.html_body + ) + end + + test "confirming a confirmed user does not trigger post-register actions" do + user = insert(:user, confirmation_pending: false, approval_pending: true) + User.confirm(user) + + ObanHelpers.perform_all() + + assert_no_email_sent() + end end describe "delete" do @@ -1415,10 +1523,10 @@ defmodule Pleroma.UserTest do test "it deactivates a user, all follow relationships and all activities", %{user: user} do follower = insert(:user) - {:ok, follower} = User.follow(follower, user) + {:ok, follower, user} = User.follow(follower, user) locked_user = insert(:user, name: "locked", is_locked: true) - {:ok, _} = User.follow(user, locked_user, :follow_pending) + {:ok, _, _} = User.follow(user, locked_user, :follow_pending) object = insert(:note, user: user) activity = insert(:note_activity, user: user, note: object) @@ -1776,9 +1884,9 @@ defmodule Pleroma.UserTest do follower2 = insert(:user) follower3 = insert(:user) - {:ok, follower} = User.follow(follower, user) - {:ok, _follower2} = User.follow(follower2, user) - {:ok, _follower3} = User.follow(follower3, user) + {:ok, follower, user} = User.follow(follower, user) + {:ok, _follower2, _user} = User.follow(follower2, user) + {:ok, _follower3, _user} = User.follow(follower3, user) {:ok, _user_relationship} = User.block(user, follower) user = refresh_record(user) @@ -1889,24 +1997,6 @@ defmodule Pleroma.UserTest do end end - describe "toggle_confirmation/1" do - test "if user is confirmed" do - user = insert(:user, confirmation_pending: false) - {:ok, user} = User.toggle_confirmation(user) - - assert user.confirmation_pending - assert user.confirmation_token - end - - test "if user is unconfirmed" do - user = insert(:user, confirmation_pending: true, confirmation_token: "some token") - {:ok, user} = User.toggle_confirmation(user) - - refute user.confirmation_pending - refute user.confirmation_token - end - end - describe "ensure_keys_present" do test "it creates keys for a user and stores them in info" do user = insert(:user) @@ -2019,8 +2109,7 @@ defmodule Pleroma.UserTest do assert other_user.following_count == 0 assert other_user.follower_count == 0 - {:ok, user} = Pleroma.User.follow(user, other_user) - other_user = Pleroma.User.get_by_id(other_user.id) + {:ok, user, other_user} = Pleroma.User.follow(user, other_user) assert user.following_count == 1 assert other_user.follower_count == 1 @@ -2043,8 +2132,7 @@ defmodule Pleroma.UserTest do assert other_user.follower_count == 0 Pleroma.Config.put([:instance, :external_user_synchronization], true) - {:ok, _user} = User.follow(user, other_user) - other_user = User.get_by_id(other_user.id) + {:ok, _user, other_user} = User.follow(user, other_user) assert other_user.follower_count == 437 end @@ -2066,7 +2154,7 @@ defmodule Pleroma.UserTest do assert other_user.follower_count == 0 Pleroma.Config.put([:instance, :external_user_synchronization], true) - {:ok, other_user} = User.follow(other_user, user) + {:ok, other_user, _user} = User.follow(other_user, user) assert other_user.following_count == 152 end @@ -2178,4 +2266,9 @@ defmodule Pleroma.UserTest do assert User.avatar_url(user, no_default: true) == nil end + + test "get_host/1" do + user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain") + assert User.get_host(user) == "lain.com" + end end diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index c9b421489..0063d0482 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -675,7 +675,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do recipient = insert(:user) actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"}) - {:ok, recipient} = User.follow(recipient, actor) + {:ok, recipient, actor} = User.follow(recipient, actor) object = data["object"] diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 6cc25dd9e..9eb7ae86b 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -726,7 +726,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"}) blocker = insert(:user) - {:ok, blocker} = User.follow(blocker, domain_user) + {:ok, blocker, domain_user} = User.follow(blocker, domain_user) {:ok, blocker} = User.block_domain(blocker, domain) assert User.following?(blocker, domain_user) @@ -853,7 +853,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do user = insert(:user) booster = insert(:user) - {:ok, user} = User.follow(user, booster) + {:ok, user, booster} = User.follow(user, booster) {:ok, announce} = CommonAPI.repeat(activity_three.id, booster) @@ -1158,13 +1158,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do user2 = insert(:user) user3 = insert(:user) - {:ok, user1} = User.follow(user1, user3) + {:ok, user1, user3} = User.follow(user1, user3) assert User.following?(user1, user3) - {:ok, user2} = User.follow(user2, user3) + {:ok, user2, user3} = User.follow(user2, user3) assert User.following?(user2, user3) - {:ok, user3} = User.follow(user3, user2) + {:ok, user3, user2} = User.follow(user3, user2) assert User.following?(user3, user2) {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"}) @@ -1931,13 +1931,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do defp public_messages(_) do [u1, u2, u3, u4] = insert_list(4, :user) - {:ok, u1} = User.follow(u1, u2) - {:ok, u2} = User.follow(u2, u1) - {:ok, u1} = User.follow(u1, u4) - {:ok, u4} = User.follow(u4, u1) + {:ok, u1, u2} = User.follow(u1, u2) + {:ok, u2, u1} = User.follow(u2, u1) + {:ok, u1, u4} = User.follow(u1, u4) + {:ok, u4, u1} = User.follow(u4, u1) - {:ok, u2} = User.follow(u2, u3) - {:ok, u3} = User.follow(u3, u2) + {:ok, u2, u3} = User.follow(u2, u3) + {:ok, u3, u2} = User.follow(u3, u2) {:ok, a1} = CommonAPI.post(u1, %{status: "Status"}) @@ -2030,15 +2030,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do defp private_messages(_) do [u1, u2, u3, u4] = insert_list(4, :user) - {:ok, u1} = User.follow(u1, u2) - {:ok, u2} = User.follow(u2, u1) - {:ok, u1} = User.follow(u1, u3) - {:ok, u3} = User.follow(u3, u1) - {:ok, u1} = User.follow(u1, u4) - {:ok, u4} = User.follow(u4, u1) - - {:ok, u2} = User.follow(u2, u3) - {:ok, u3} = User.follow(u3, u2) + {:ok, u1, u2} = User.follow(u1, u2) + {:ok, u2, u1} = User.follow(u2, u1) + {:ok, u1, u3} = User.follow(u1, u3) + {:ok, u3, u1} = User.follow(u3, u1) + {:ok, u1, u4} = User.follow(u1, u4) + {:ok, u4, u1} = User.follow(u4, u1) + + {:ok, u2, u3} = User.follow(u2, u3) + {:ok, u3, u2} = User.follow(u3, u2) {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"}) diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index b9388b966..3503d25b2 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -281,8 +281,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do actor = insert(:user, follower_address: follower.ap_id) user = insert(:user) - {:ok, _follower_one} = Pleroma.User.follow(follower, actor) - actor = refresh_record(actor) + {:ok, follower, actor} = Pleroma.User.follow(follower, actor) note_activity = insert(:note_activity, diff --git a/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs index 0d431df18..485216487 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.AcceptHandlingTest do follower = insert(:user) followed = insert(:user) - {:ok, follower} = User.follow(follower, followed) + {:ok, follower, followed} = User.follow(follower, followed) assert User.following?(follower, followed) == true {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) diff --git a/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs index b8e4ad827..679c33c6c 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs @@ -40,8 +40,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.BlockHandlingTest do |> Map.put("object", blocked.ap_id) |> Map.put("actor", blocker.ap_id) - {:ok, blocker} = User.follow(blocker, blocked) - {:ok, blocked} = User.follow(blocked, blocker) + {:ok, blocker, blocked} = User.follow(blocker, blocked) + {:ok, blocked, blocker} = User.follow(blocked, blocker) assert User.following?(blocker, blocked) assert User.following?(blocked, blocker) diff --git a/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs index cc28eb7ef..5a3bef792 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs @@ -35,7 +35,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.RejectHandlingTest do follower = insert(:user) followed = insert(:user, is_locked: true) - {:ok, follower} = User.follow(follower, followed) + {:ok, follower, followed} = User.follow(follower, followed) {:ok, _, _, follow_activity} = CommonAPI.follow(follower, followed) assert User.following?(follower, followed) == true diff --git a/test/pleroma/web/activity_pub/visibility_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs index 8e9354c65..836d44994 100644 --- a/test/pleroma/web/activity_pub/visibility_test.exs +++ b/test/pleroma/web/activity_pub/visibility_test.exs @@ -15,7 +15,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do mentioned = insert(:user) following = insert(:user) unrelated = insert(:user) - {:ok, following} = Pleroma.User.follow(following, user) + {:ok, following, user} = Pleroma.User.follow(following, user) {:ok, list} = Pleroma.List.create("foo", user) Pleroma.List.follow(list, unrelated) diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index c06ae55ca..e50d1425b 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -941,7 +941,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do describe "/api/pleroma/admin/stats" do test "status visibility count", %{conn: conn} do - admin = insert(:user, is_admin: true) user = insert(:user) CommonAPI.post(user, %{visibility: "public", status: "hey"}) CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) @@ -949,7 +948,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do response = conn - |> assign(:user, admin) |> get("/api/pleroma/admin/stats") |> json_response(200) @@ -958,7 +956,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do end test "by instance", %{conn: conn} do - admin = insert(:user, is_admin: true) user1 = insert(:user) instance2 = "instance2.tld" user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"}) @@ -969,7 +966,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do response = conn - |> assign(:user, admin) |> get("/api/pleroma/admin/stats", instance: instance2) |> json_response(200) diff --git a/test/pleroma/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs index 276e827d1..e6b203e74 100644 --- a/test/pleroma/web/admin_api/controllers/config_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/config_controller_test.exs @@ -1417,11 +1417,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do describe "GET /api/pleroma/admin/config/descriptions" do test "structure", %{conn: conn} do - admin = insert(:user, is_admin: true) - - conn = - assign(conn, :user, admin) - |> get("/api/pleroma/admin/config/descriptions") + conn = get(conn, "/api/pleroma/admin/config/descriptions") assert [child | _others] = json_response_and_validate_schema(conn, 200) @@ -1439,11 +1435,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do {:esshd} ]) - admin = insert(:user, is_admin: true) - - conn = - assign(conn, :user, admin) - |> get("/api/pleroma/admin/config/descriptions") + conn = get(conn, "/api/pleroma/admin/config/descriptions") children = json_response_and_validate_schema(conn, 200) diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs index e4084b0e5..b4abcf6f2 100644 --- a/test/pleroma/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -131,7 +131,7 @@ defmodule Pleroma.Web.Feed.TagControllerTest do '#{Pleroma.Web.base_url()}/tags/pleromaart.rss' assert xpath(xml, ~x"//channel/webfeeds:logo/text()") == - '#{Pleroma.Web.base_url()}/static/logo.png' + '#{Pleroma.Web.base_url()}/static/logo.svg' assert xpath(xml, ~x"//channel/item/title/text()"l) == [ '42 This is :moominmamm...', diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index e8a00dd6b..3361c8669 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -320,7 +320,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do user_two = insert(:user) user_three = insert(:user) - {:ok, _user_three} = User.follow(user_three, user_one) + {:ok, _user_three, _user_one} = User.follow(user_three, user_one) {:ok, activity} = CommonAPI.post(user_one, %{status: "HI!!!"}) @@ -568,7 +568,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "getting followers", %{user: user, conn: conn} do other_user = insert(:user) - {:ok, %{id: user_id}} = User.follow(user, other_user) + {:ok, %{id: user_id}, other_user} = User.follow(user, other_user) conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") @@ -577,7 +577,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "getting followers, hide_followers", %{user: user, conn: conn} do other_user = insert(:user, hide_followers: true) - {:ok, _user} = User.follow(user, other_user) + {:ok, _user, _other_user} = User.follow(user, other_user) conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") @@ -587,7 +587,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "getting followers, hide_followers, same user requesting" do user = insert(:user) other_user = insert(:user, hide_followers: true) - {:ok, _user} = User.follow(user, other_user) + {:ok, _user, _other_user} = User.follow(user, other_user) conn = build_conn() @@ -599,9 +599,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do end test "getting followers, pagination", %{user: user, conn: conn} do - {:ok, %User{id: follower1_id}} = :user |> insert() |> User.follow(user) - {:ok, %User{id: follower2_id}} = :user |> insert() |> User.follow(user) - {:ok, %User{id: follower3_id}} = :user |> insert() |> User.follow(user) + {:ok, %User{id: follower1_id}, _user} = :user |> insert() |> User.follow(user) + {:ok, %User{id: follower2_id}, _user} = :user |> insert() |> User.follow(user) + {:ok, %User{id: follower3_id}, _user} = :user |> insert() |> User.follow(user) assert [%{"id" => ^follower3_id}, %{"id" => ^follower2_id}] = conn @@ -637,7 +637,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "getting following", %{user: user, conn: conn} do other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) + {:ok, user, other_user} = User.follow(user, other_user) conn = get(conn, "/api/v1/accounts/#{user.id}/following") @@ -648,7 +648,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "getting following, hide_follows, other user requesting" do user = insert(:user, hide_follows: true) other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) + {:ok, user, other_user} = User.follow(user, other_user) conn = build_conn() @@ -662,7 +662,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "getting following, hide_follows, same user requesting" do user = insert(:user, hide_follows: true) other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) + {:ok, user, _other_user} = User.follow(user, other_user) conn = build_conn() @@ -677,9 +677,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do following1 = insert(:user) following2 = insert(:user) following3 = insert(:user) - {:ok, _} = User.follow(user, following1) - {:ok, _} = User.follow(user, following2) - {:ok, _} = User.follow(user, following3) + {:ok, _, _} = User.follow(user, following1) + {:ok, _, _} = User.follow(user, following2) + {:ok, _, _} = User.follow(user, following3) res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") @@ -1520,7 +1520,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "returns the relationships for the current user", %{user: user, conn: conn} do %{id: other_user_id} = other_user = insert(:user) - {:ok, _user} = User.follow(user, other_user) + {:ok, _user, _other_user} = User.follow(user, other_user) assert [%{"id" => ^other_user_id}] = conn diff --git a/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs index bf2438fe2..d7834c876 100644 --- a/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs @@ -39,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do |> get("/web/login", %{code: auth.token}) assert conn.status == 302 - assert redirected_to(conn) == path + assert redirected_to(conn) =~ path end test "redirects to the getting-started page when referer is not present", %{conn: conn} do @@ -49,7 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do conn = get(conn, "/web/login", %{code: auth.token}) assert conn.status == 302 - assert redirected_to(conn) == "/web/getting-started" + assert redirected_to(conn) =~ "/web/getting-started" end end diff --git a/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs index c67e584dd..b00615ac9 100644 --- a/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs @@ -18,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do user_two = insert(:user) user_three = insert(:user) - {:ok, user_two} = User.follow(user_two, user_one) + {:ok, user_two, user_one} = User.follow(user_two, user_one) {:ok, %{user: user_one, user_two: user_two, user_three: user_three, conn: conn}} end diff --git a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs index a9dd7cd30..b977b41ae 100644 --- a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do other_user = insert(:user) {:ok, _, _, _activity} = CommonAPI.follow(other_user, user) - {:ok, other_user} = User.follow(other_user, user, :follow_pending) + {:ok, other_user, user} = User.follow(other_user, user, :follow_pending) assert User.following?(other_user, user) == false @@ -35,7 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do other_user = insert(:user) {:ok, _, _, _activity} = CommonAPI.follow(other_user, user) - {:ok, other_user} = User.follow(other_user, user, :follow_pending) + {:ok, other_user, user} = User.follow(other_user, user, :follow_pending) user = User.get_cached_by_id(user.id) other_user = User.get_cached_by_id(other_user.id) diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index 8356b64d3..655e35ac6 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -136,7 +136,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do test "doesn't return replies if follower is posting with blocked user" do %{conn: conn, user: blocker} = oauth_access(["read:statuses"]) [blockee, friend] = insert_list(2, :user) - {:ok, blocker} = User.follow(blocker, friend) + {:ok, blocker, friend} = User.follow(blocker, friend) {:ok, _} = User.block(blocker, blockee) conn = assign(conn, :user, blocker) @@ -165,7 +165,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do %{conn: conn, user: blocker} = oauth_access(["read:statuses"]) friend = insert(:user) blockee = insert(:user, ap_id: "https://example.com/users/blocked") - {:ok, blocker} = User.follow(blocker, friend) + {:ok, blocker, friend} = User.follow(blocker, friend) {:ok, blocker} = User.block_domain(blocker, "example.com") conn = assign(conn, :user, blocker) @@ -336,7 +336,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do user_one = insert(:user) user_two = insert(:user) - {:ok, user_two} = User.follow(user_two, user_one) + {:ok, user_two, user_one} = User.follow(user_two, user_one) {:ok, direct} = CommonAPI.post(user_one, %{ diff --git a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs index ed8add8d2..b9cd050df 100644 --- a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs +++ b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs @@ -64,7 +64,8 @@ defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do end test "does not redirect logged in users to the login page", %{conn: conn, path: path} do - token = insert(:oauth_token, scopes: ["read"]) + {:ok, app} = Pleroma.Web.MastodonAPI.AuthController.local_mastofe_app() + token = insert(:oauth_token, app: app, scopes: ["read"]) conn = conn diff --git a/test/pleroma/web/mastodon_api/mastodon_api_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_test.exs index 0c5a38bf6..687fe5585 100644 --- a/test/pleroma/web/mastodon_api/mastodon_api_test.exs +++ b/test/pleroma/web/mastodon_api/mastodon_api_test.exs @@ -30,7 +30,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do test "returns ok if user already followed" do follower = insert(:user) user = insert(:user) - {:ok, follower} = User.follow(follower, user) + {:ok, follower, user} = User.follow(follower, user) {:ok, follower} = MastodonAPI.follow(follower, refresh_record(user)) assert User.following?(follower, user) end @@ -41,8 +41,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do follower1_user = insert(:user) follower2_user = insert(:user) user = insert(:user) - {:ok, _follower1_user} = User.follow(follower1_user, user) - {:ok, follower2_user} = User.follow(follower2_user, user) + {:ok, _follower1_user, _user} = User.follow(follower1_user, user) + {:ok, follower2_user, _user} = User.follow(follower2_user, user) assert MastodonAPI.get_followers(user, %{"limit" => 1}) == [follower2_user] end @@ -55,9 +55,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do followed_two = insert(:user) followed_three = insert(:user) - {:ok, user} = User.follow(user, followed_one) - {:ok, user} = User.follow(user, followed_two) - {:ok, user} = User.follow(user, followed_three) + {:ok, user, followed_one} = User.follow(user, followed_one) + {:ok, user, followed_two} = User.follow(user, followed_two) + {:ok, user, followed_three} = User.follow(user, followed_three) res = MastodonAPI.get_friends(user) assert length(res) == 3 diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index 139e32362..8c77f14d4 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -274,8 +274,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do user = insert(:user) other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) - {:ok, other_user} = User.follow(other_user, user) + {:ok, user, other_user} = User.follow(user, other_user) + {:ok, other_user, user} = User.follow(other_user, user) {:ok, _subscription} = User.subscribe(user, other_user) {:ok, _user_relationships} = User.mute(user, other_user, %{notifications: true}) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user) @@ -301,7 +301,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do user = insert(:user) other_user = insert(:user) - {:ok, user} = User.follow(user, other_user) + {:ok, user, other_user} = User.follow(user, other_user) {:ok, _subscription} = User.subscribe(user, other_user) {:ok, _user_relationship} = User.block(user, other_user) {:ok, _user_relationship} = User.block(other_user, user) diff --git a/test/pleroma/web/o_auth/mfa_controller_test.exs b/test/pleroma/web/o_auth/mfa_controller_test.exs index 3c341facd..6ecd0f6c9 100644 --- a/test/pleroma/web/o_auth/mfa_controller_test.exs +++ b/test/pleroma/web/o_auth/mfa_controller_test.exs @@ -171,7 +171,6 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do assert match?( %{ "access_token" => _, - "expires_in" => 600, "me" => ^ap_id, "refresh_token" => _, "scope" => "write", @@ -280,7 +279,6 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do assert match?( %{ "access_token" => _, - "expires_in" => 600, "me" => ^ap_id, "refresh_token" => _, "scope" => "write", diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs index c6526d8c9..ac22856ea 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -4,8 +4,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do use Pleroma.Web.ConnCase + import Pleroma.Factory + alias Pleroma.Helpers.AuthHelper alias Pleroma.MFA alias Pleroma.MFA.TOTP alias Pleroma.Repo @@ -454,7 +456,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -478,7 +480,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -501,7 +503,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -527,7 +529,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -551,7 +553,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do conn = conn - |> put_session(:oauth_token, token.token) + |> AuthHelper.put_session_token(token.token) |> get( "/oauth/authorize", %{ @@ -609,6 +611,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do end end + test "authorize from cookie" do + user = insert(:user) + app = insert(:oauth_app) + oauth_token = insert(:oauth_token, user: user, app: app) + redirect_uri = OAuthController.default_redirect_uri(app) + + conn = + build_conn() + |> Plug.Session.call(Plug.Session.init(@session_opts)) + |> fetch_session() + |> AuthHelper.put_session_token(oauth_token.token) + |> post( + "/oauth/authorize", + %{ + "authorization" => %{ + "name" => user.nickname, + "client_id" => app.client_id, + "redirect_uri" => redirect_uri, + "scope" => app.scopes, + "state" => "statepassed" + } + } + ) + + target = redirected_to(conn) + assert target =~ redirect_uri + + query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + + assert %{"state" => "statepassed", "code" => code} = query + auth = Repo.get_by(Authorization, token: code) + assert auth + assert auth.scopes == app.scopes + end + test "redirect to on two-factor auth page" do otp_secret = TOTP.generate_secret() @@ -1068,7 +1105,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do %{ "scope" => "write", "token_type" => "Bearer", - "expires_in" => 600, "access_token" => _, "refresh_token" => _, "me" => ^ap_id @@ -1108,7 +1144,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do %{ "scope" => "write", "token_type" => "Bearer", - "expires_in" => 600, "access_token" => _, "refresh_token" => _, "me" => ^ap_id @@ -1191,7 +1226,6 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do %{ "scope" => "write", "token_type" => "Bearer", - "expires_in" => 600, "access_token" => _, "refresh_token" => _, "me" => ^ap_id @@ -1219,8 +1253,43 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do end end - describe "POST /oauth/revoke - bad request" do - test "returns 500" do + describe "POST /oauth/revoke" do + test "when authenticated with request token, revokes it and clears it from session" do + oauth_token = insert(:oauth_token) + + conn = + build_conn() + |> Plug.Session.call(Plug.Session.init(@session_opts)) + |> fetch_session() + |> AuthHelper.put_session_token(oauth_token.token) + |> post("/oauth/revoke", %{"token" => oauth_token.token}) + + assert json_response(conn, 200) + + refute AuthHelper.get_session_token(conn) + assert Token.get_by_token(oauth_token.token) == {:error, :not_found} + end + + test "if request is authenticated with a different token, " <> + "revokes requested token but keeps session token" do + user = insert(:user) + oauth_token = insert(:oauth_token, user: user) + other_app_oauth_token = insert(:oauth_token, user: user) + + conn = + build_conn() + |> Plug.Session.call(Plug.Session.init(@session_opts)) + |> fetch_session() + |> AuthHelper.put_session_token(oauth_token.token) + |> post("/oauth/revoke", %{"token" => other_app_oauth_token.token}) + + assert json_response(conn, 200) + + assert AuthHelper.get_session_token(conn) == oauth_token.token + assert Token.get_by_token(other_app_oauth_token.token) == {:error, :not_found} + end + + test "returns 500 on bad request" do response = build_conn() |> post("/oauth/revoke", %{}) diff --git a/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs index c1e6a8cc5..a6c9d0c1b 100644 --- a/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs @@ -264,9 +264,10 @@ defmodule Pleroma.Web.PleromaAPI.ChatControllerTest do assert length(result) == 3 # Trying to get the chat of a different user + other_user_chat = Chat.get(other_user.id, user.ap_id) + conn - |> assign(:user, other_user) - |> get("/api/v1/pleroma/chats/#{chat.id}/messages") + |> get("/api/v1/pleroma/chats/#{other_user_chat.id}/messages") |> json_response_and_validate_schema(404) end end diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs index 82de86ee3..6fbdaec7a 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do use Pleroma.Web.ConnCase + import Mock import Tesla.Mock import Pleroma.Factory @@ -200,6 +201,31 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do } end + test "returns an error on add file when file system is not writable", %{ + admin_conn: admin_conn + } do + pack_file = Path.join([@emoji_path, "not_loaded", "pack.json"]) + + with_mocks([ + {File, [:passthrough], [stat: fn ^pack_file -> {:error, :eacces} end]} + ]) do + assert admin_conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/pleroma/emoji/packs/files?name=not_loaded", %{ + shortcode: "blank3", + filename: "dir/blank.png", + file: %Plug.Upload{ + filename: "blank.png", + path: "#{@emoji_path}/test_pack/blank.png" + } + }) + |> json_response_and_validate_schema(500) == %{ + "error" => + "Unexpected error occurred while adding file to pack. (POSIX error: Permission denied)" + } + end + end + test "remove file with not loaded pack", %{admin_conn: admin_conn} do assert admin_conn |> delete("/api/pleroma/emoji/packs/files?name=not_loaded&shortcode=blank3") diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs index 3445f0ca0..d9385389b 100644 --- a/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs @@ -3,8 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do - use Pleroma.Web.ConnCase + use Pleroma.Web.ConnCase, async: false + import Mock import Tesla.Mock import Pleroma.Factory @@ -346,7 +347,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do end end - describe "PATCH /api/pleroma/emoji/pack?name=:name" do + describe "PATCH/update /api/pleroma/emoji/pack?name=:name" do setup do pack_file = "#{@emoji_path}/test_pack/pack.json" original_content = File.read!(pack_file) @@ -365,6 +366,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do }} end + test "returns error when file system not writable", %{admin_conn: conn} = ctx do + with_mocks([ + {File, [:passthrough], [stat: fn _ -> {:error, :eacces} end]} + ]) do + assert conn + |> put_req_header("content-type", "multipart/form-data") + |> patch( + "/api/pleroma/emoji/pack?name=test_pack", + %{"metadata" => ctx[:new_data]} + ) + |> json_response_and_validate_schema(500) + end + end + test "for a pack without a fallback source", ctx do assert ctx[:admin_conn] |> put_req_header("content-type", "multipart/form-data") @@ -424,6 +439,46 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackControllerTest do end describe "POST/DELETE /api/pleroma/emoji/pack?name=:name" do + test "returns an error on creates pack when file system not writable", %{ + admin_conn: admin_conn + } do + path_pack = Path.join(@emoji_path, "test_pack") + + with_mocks([ + {File, [:passthrough], [mkdir: fn ^path_pack -> {:error, :eacces} end]} + ]) do + assert admin_conn + |> post("/api/pleroma/emoji/pack?name=test_pack") + |> json_response_and_validate_schema(500) == %{ + "error" => + "Unexpected error occurred while creating pack. (POSIX error: Permission denied)" + } + end + end + + test "returns an error on deletes pack when the file system is not writable", %{ + admin_conn: admin_conn + } do + path_pack = Path.join(@emoji_path, "test_emoji_pack") + + try do + {:ok, _pack} = Pleroma.Emoji.Pack.create("test_emoji_pack") + + with_mocks([ + {File, [:passthrough], [rm_rf: fn ^path_pack -> {:error, :eacces, path_pack} end]} + ]) do + assert admin_conn + |> delete("/api/pleroma/emoji/pack?name=test_emoji_pack") + |> json_response_and_validate_schema(500) == %{ + "error" => + "Couldn't delete the `test_emoji_pack` pack (POSIX error: Permission denied)" + } + end + after + File.rm_rf(path_pack) + end + end + test "creating and deleting a pack", %{admin_conn: admin_conn} do assert admin_conn |> post("/api/pleroma/emoji/pack?name=test_created") diff --git a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs index 68723de71..d83d33912 100644 --- a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs @@ -47,7 +47,8 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do |> json_response_and_validate_schema(200) assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [user2] + assert job_result == [refresh_record(user2)] + assert [%Pleroma.User{follower_count: 1}] = job_result end end @@ -108,7 +109,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do |> json_response_and_validate_schema(200) assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == users + assert job_result == Enum.map(users, &refresh_record/1) end end diff --git a/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs b/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs index 33394722a..23498badf 100644 --- a/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs +++ b/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs @@ -49,6 +49,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlugTest do |> AdminSecretAuthenticationPlug.call(%{}) assert conn.assigns[:user].is_admin + assert conn.assigns[:token] == nil assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end @@ -69,6 +70,7 @@ defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlugTest do |> AdminSecretAuthenticationPlug.call(%{}) assert conn.assigns[:user].is_admin + assert conn.assigns[:token] == nil assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end end diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs index af39352e2..3dedd38b2 100644 --- a/test/pleroma/web/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -48,6 +48,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do |> AuthenticationPlug.call(%{}) assert conn.assigns.user == conn.assigns.auth_user + assert conn.assigns.token == nil assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) end @@ -62,6 +63,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do |> AuthenticationPlug.call(%{}) assert conn.assigns.user.id == conn.assigns.auth_user.id + assert conn.assigns.token == nil assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) user = User.get_by_id(user.id) @@ -83,6 +85,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do |> AuthenticationPlug.call(%{}) assert conn.assigns.user.id == conn.assigns.auth_user.id + assert conn.assigns.token == nil assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) user = User.get_by_id(user.id) diff --git a/test/pleroma/web/plugs/ensure_user_key_plug_test.exs b/test/pleroma/web/plugs/ensure_user_key_plug_test.exs deleted file mode 100644 index f912ef755..000000000 --- a/test/pleroma/web/plugs/ensure_user_key_plug_test.exs +++ /dev/null @@ -1,29 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.EnsureUserKeyPlugTest do - use Pleroma.Web.ConnCase, async: true - - alias Pleroma.Web.Plugs.EnsureUserKeyPlug - - test "if the conn has a user key set, it does nothing", %{conn: conn} do - conn = - conn - |> assign(:user, 1) - - ret_conn = - conn - |> EnsureUserKeyPlug.call(%{}) - - assert conn == ret_conn - end - - test "if the conn has no key set, it sets it to nil", %{conn: conn} do - conn = - conn - |> EnsureUserKeyPlug.call(%{}) - - assert Map.has_key?(conn.assigns, :user) - end -end diff --git a/test/pleroma/web/plugs/ensure_user_token_assigns_plug_test.exs b/test/pleroma/web/plugs/ensure_user_token_assigns_plug_test.exs new file mode 100644 index 000000000..9592820c7 --- /dev/null +++ b/test/pleroma/web/plugs/ensure_user_token_assigns_plug_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureUserTokenAssignsPlugTest do + use Pleroma.Web.ConnCase, async: true + + import Pleroma.Factory + + alias Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug + + test "with :user assign set to a User record " <> + "and :token assign set to a Token belonging to this user, " <> + "it does nothing" do + %{conn: conn} = oauth_access(["read"]) + + ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{}) + + assert conn == ret_conn + end + + test "with :user assign set to a User record " <> + "but :token assign not set or not a Token, " <> + "it assigns :token to `nil`", + %{conn: conn} do + user = insert(:user) + conn = assign(conn, :user, user) + + ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{}) + + assert %{token: nil} = ret_conn.assigns + + ret_conn2 = + conn + |> assign(:token, 1) + |> EnsureUserTokenAssignsPlug.call(%{}) + + assert %{token: nil} = ret_conn2.assigns + end + + # Abnormal (unexpected) scenario + test "with :user assign set to a User record " <> + "but :token assign set to a Token NOT belonging to :user, " <> + "it drops auth info" do + %{conn: conn} = oauth_access(["read"]) + other_user = insert(:user) + + conn = assign(conn, :user, other_user) + + ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{}) + + assert %{user: nil, token: nil} = ret_conn.assigns + end + + test "if :user assign is not set to a User record, it sets :user and :token to nil", %{ + conn: conn + } do + ret_conn = EnsureUserTokenAssignsPlug.call(conn, %{}) + + assert %{user: nil, token: nil} = ret_conn.assigns + + ret_conn2 = + conn + |> assign(:user, 1) + |> EnsureUserTokenAssignsPlug.call(%{}) + + assert %{user: nil, token: nil} = ret_conn2.assigns + end +end diff --git a/test/pleroma/web/plugs/legacy_authentication_plug_test.exs b/test/pleroma/web/plugs/legacy_authentication_plug_test.exs deleted file mode 100644 index 2016a31a8..000000000 --- a/test/pleroma/web/plugs/legacy_authentication_plug_test.exs +++ /dev/null @@ -1,82 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlugTest do - use Pleroma.Web.ConnCase - - import Pleroma.Factory - - alias Pleroma.User - alias Pleroma.Web.Plugs.LegacyAuthenticationPlug - alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Web.Plugs.PlugHelper - - setup do - user = - insert(:user, - password: "password", - password_hash: - "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" - ) - - %{user: user} - end - - test "it does nothing if a user is assigned", %{conn: conn, user: user} do - conn = - conn - |> assign(:auth_credentials, %{username: "dude", password: "password"}) - |> assign(:auth_user, user) - |> assign(:user, %User{}) - - ret_conn = - conn - |> LegacyAuthenticationPlug.call(%{}) - - assert ret_conn == conn - end - - @tag :skip_on_mac - test "if `auth_user` is present and password is correct, " <> - "it authenticates the user, resets the password, marks OAuthScopesPlug as skipped", - %{ - conn: conn, - user: user - } do - conn = - conn - |> assign(:auth_credentials, %{username: "dude", password: "password"}) - |> assign(:auth_user, user) - - conn = LegacyAuthenticationPlug.call(conn, %{}) - - assert conn.assigns.user.id == user.id - assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) - end - - @tag :skip_on_mac - test "it does nothing if the password is wrong", %{ - conn: conn, - user: user - } do - conn = - conn - |> assign(:auth_credentials, %{username: "dude", password: "wrong_password"}) - |> assign(:auth_user, user) - - ret_conn = - conn - |> LegacyAuthenticationPlug.call(%{}) - - assert conn == ret_conn - end - - test "with no credentials or user it does nothing", %{conn: conn} do - ret_conn = - conn - |> LegacyAuthenticationPlug.call(%{}) - - assert ret_conn == conn - end -end diff --git a/test/pleroma/web/plugs/o_auth_plug_test.exs b/test/pleroma/web/plugs/o_auth_plug_test.exs index b9d722f76..1186cdb14 100644 --- a/test/pleroma/web/plugs/o_auth_plug_test.exs +++ b/test/pleroma/web/plugs/o_auth_plug_test.exs @@ -5,43 +5,49 @@ defmodule Pleroma.Web.Plugs.OAuthPlugTest do use Pleroma.Web.ConnCase, async: true + alias Pleroma.Helpers.AuthHelper + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.Token.Strategy.Revoke alias Pleroma.Web.Plugs.OAuthPlug - import Pleroma.Factory + alias Plug.Session - @session_opts [ - store: :cookie, - key: "_test", - signing_salt: "cooldude" - ] + import Pleroma.Factory setup %{conn: conn} do user = insert(:user) - {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create(insert(:oauth_app), user) - %{user: user, token: token, conn: conn} + {:ok, oauth_token} = Token.create(insert(:oauth_app), user) + %{user: user, token: oauth_token, conn: conn} + end + + test "it does nothing if a user is assigned", %{conn: conn} do + conn = assign(conn, :user, %Pleroma.User{}) + ret_conn = OAuthPlug.call(conn, %{}) + + assert ret_conn == conn end - test "with valid token(uppercase), it assigns the user", %{conn: conn} = opts do + test "with valid token (uppercase) in auth header, it assigns the user", %{conn: conn} = opts do conn = conn - |> put_req_header("authorization", "BEARER #{opts[:token]}") + |> put_req_header("authorization", "BEARER #{opts[:token].token}") |> OAuthPlug.call(%{}) assert conn.assigns[:user] == opts[:user] end - test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do + test "with valid token (downcase) in auth header, it assigns the user", %{conn: conn} = opts do conn = conn - |> put_req_header("authorization", "bearer #{opts[:token]}") + |> put_req_header("authorization", "bearer #{opts[:token].token}") |> OAuthPlug.call(%{}) assert conn.assigns[:user] == opts[:user] end - test "with valid token(downcase) in url parameters, it assigns the user", opts do + test "with valid token (downcase) in url parameters, it assigns the user", opts do conn = :get - |> build_conn("/?access_token=#{opts[:token]}") + |> build_conn("/?access_token=#{opts[:token].token}") |> put_req_header("content-type", "application/json") |> fetch_query_params() |> OAuthPlug.call(%{}) @@ -49,16 +55,16 @@ defmodule Pleroma.Web.Plugs.OAuthPlugTest do assert conn.assigns[:user] == opts[:user] end - test "with valid token(downcase) in body parameters, it assigns the user", opts do + test "with valid token (downcase) in body parameters, it assigns the user", opts do conn = :post - |> build_conn("/api/v1/statuses", access_token: opts[:token], status: "test") + |> build_conn("/api/v1/statuses", access_token: opts[:token].token, status: "test") |> OAuthPlug.call(%{}) assert conn.assigns[:user] == opts[:user] end - test "with invalid token, it not assigns the user", %{conn: conn} do + test "with invalid token, it does not assign the user", %{conn: conn} do conn = conn |> put_req_header("authorization", "bearer TTTTT") @@ -67,14 +73,56 @@ defmodule Pleroma.Web.Plugs.OAuthPlugTest do refute conn.assigns[:user] end - test "when token is missed but token in session, it assigns the user", %{conn: conn} = opts do - conn = - conn - |> Plug.Session.call(Plug.Session.init(@session_opts)) - |> fetch_session() - |> put_session(:oauth_token, opts[:token]) - |> OAuthPlug.call(%{}) - - assert conn.assigns[:user] == opts[:user] + describe "with :oauth_token in session, " do + setup %{token: oauth_token, conn: conn} do + session_opts = [ + store: :cookie, + key: "_test", + signing_salt: "cooldude" + ] + + conn = + conn + |> Session.call(Session.init(session_opts)) + |> fetch_session() + |> AuthHelper.put_session_token(oauth_token.token) + + %{conn: conn} + end + + test "if session-stored token matches a valid OAuth token, assigns :user and :token", %{ + conn: conn, + user: user, + token: oauth_token + } do + conn = OAuthPlug.call(conn, %{}) + + assert conn.assigns.user && conn.assigns.user.id == user.id + assert conn.assigns.token && conn.assigns.token.id == oauth_token.id + end + + test "if session-stored token matches an expired OAuth token, does nothing", %{ + conn: conn, + token: oauth_token + } do + expired_valid_until = NaiveDateTime.add(NaiveDateTime.utc_now(), -3600 * 24, :second) + + oauth_token + |> Ecto.Changeset.change(valid_until: expired_valid_until) + |> Pleroma.Repo.update() + + ret_conn = OAuthPlug.call(conn, %{}) + assert ret_conn == conn + end + + test "if session-stored token matches a revoked OAuth token, does nothing", %{ + conn: conn, + token: oauth_token + } do + Revoke.revoke(oauth_token) + + ret_conn = OAuthPlug.call(conn, %{}) + assert ret_conn == conn + end end end diff --git a/test/pleroma/web/plugs/session_authentication_plug_test.exs b/test/pleroma/web/plugs/session_authentication_plug_test.exs deleted file mode 100644 index 2b4d5bc0c..000000000 --- a/test/pleroma/web/plugs/session_authentication_plug_test.exs +++ /dev/null @@ -1,63 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Plugs.SessionAuthenticationPlugTest do - use Pleroma.Web.ConnCase, async: true - - alias Pleroma.User - alias Pleroma.Web.Plugs.SessionAuthenticationPlug - - setup %{conn: conn} do - session_opts = [ - store: :cookie, - key: "_test", - signing_salt: "cooldude" - ] - - conn = - conn - |> Plug.Session.call(Plug.Session.init(session_opts)) - |> fetch_session - |> assign(:auth_user, %User{id: 1}) - - %{conn: conn} - end - - test "it does nothing if a user is assigned", %{conn: conn} do - conn = - conn - |> assign(:user, %User{}) - - ret_conn = - conn - |> SessionAuthenticationPlug.call(%{}) - - assert ret_conn == conn - end - - test "if the auth_user has the same id as the user_id in the session, it assigns the user", %{ - conn: conn - } do - conn = - conn - |> put_session(:user_id, conn.assigns.auth_user.id) - |> SessionAuthenticationPlug.call(%{}) - - assert conn.assigns.user == conn.assigns.auth_user - end - - test "if the auth_user has a different id as the user_id in the session, it does nothing", %{ - conn: conn - } do - conn = - conn - |> put_session(:user_id, -1) - - ret_conn = - conn - |> SessionAuthenticationPlug.call(%{}) - - assert ret_conn == conn - end -end diff --git a/test/pleroma/web/plugs/set_user_session_id_plug_test.exs b/test/pleroma/web/plugs/set_user_session_id_plug_test.exs index a89b5628f..21417d0e7 100644 --- a/test/pleroma/web/plugs/set_user_session_id_plug_test.exs +++ b/test/pleroma/web/plugs/set_user_session_id_plug_test.exs @@ -5,7 +5,7 @@ defmodule Pleroma.Web.Plugs.SetUserSessionIdPlugTest do use Pleroma.Web.ConnCase, async: true - alias Pleroma.User + alias Pleroma.Helpers.AuthHelper alias Pleroma.Web.Plugs.SetUserSessionIdPlug setup %{conn: conn} do @@ -18,28 +18,26 @@ defmodule Pleroma.Web.Plugs.SetUserSessionIdPlugTest do conn = conn |> Plug.Session.call(Plug.Session.init(session_opts)) - |> fetch_session + |> fetch_session() %{conn: conn} end test "doesn't do anything if the user isn't set", %{conn: conn} do - ret_conn = - conn - |> SetUserSessionIdPlug.call(%{}) + ret_conn = SetUserSessionIdPlug.call(conn, %{}) assert ret_conn == conn end - test "sets the user_id in the session to the user id of the user assign", %{conn: conn} do - Code.ensure_compiled(Pleroma.User) + test "sets session token basing on :token assign", %{conn: conn} do + %{user: user, token: oauth_token} = oauth_access(["read"]) - conn = + ret_conn = conn - |> assign(:user, %User{id: 1}) + |> assign(:user, user) + |> assign(:token, oauth_token) |> SetUserSessionIdPlug.call(%{}) - id = get_session(conn, :user_id) - assert id == 1 + assert AuthHelper.get_session_token(ret_conn) == oauth_token.token end end diff --git a/test/pleroma/web/preload/providers/instance_test.exs b/test/pleroma/web/preload/providers/instance_test.exs index 8493f2a94..6033899b0 100644 --- a/test/pleroma/web/preload/providers/instance_test.exs +++ b/test/pleroma/web/preload/providers/instance_test.exs @@ -50,7 +50,7 @@ defmodule Pleroma.Web.Preload.Providers.InstanceTest do "/api/pleroma/frontend_configurations" => fe_configs } do assert %{ - pleroma_fe: %{background: "/images/city.jpg", logo: "/static/logo.png"} + pleroma_fe: %{background: "/images/city.jpg", logo: "/static/logo.svg"} } = fe_configs end end diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index dd210c3b5..ad66ddc9d 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -403,6 +403,67 @@ defmodule Pleroma.Web.StreamerTest do assert notif.activity.id == follow_activity.id refute Streamer.filtered_by_user?(user, notif) end + + test "it sends follow relationships updates to the 'user' stream", %{ + user: user, + token: oauth_token + } do + user_id = user.id + user_url = user.ap_id + other_user = insert(:user) + other_user_id = other_user.id + + body = + File.read!("test/fixtures/users_mock/localhost.json") + |> String.replace("{{nickname}}", user.nickname) + |> Jason.encode!() + + Tesla.Mock.mock_global(fn + %{method: :get, url: ^user_url} -> + %Tesla.Env{status: 200, body: body} + end) + + Streamer.get_topic_and_add_socket("user", user, oauth_token) + {:ok, _follower, _followed, _follow_activity} = CommonAPI.follow(user, other_user) + + assert_receive {:text, event} + + assert %{"event" => "pleroma:follow_relationships_update", "payload" => payload} = + Jason.decode!(event) + + assert %{ + "follower" => %{ + "follower_count" => 0, + "following_count" => 0, + "id" => ^user_id + }, + "following" => %{ + "follower_count" => 0, + "following_count" => 0, + "id" => ^other_user_id + }, + "state" => "follow_pending" + } = Jason.decode!(payload) + + assert_receive {:text, event} + + assert %{"event" => "pleroma:follow_relationships_update", "payload" => payload} = + Jason.decode!(event) + + assert %{ + "follower" => %{ + "follower_count" => 0, + "following_count" => 1, + "id" => ^user_id + }, + "following" => %{ + "follower_count" => 1, + "following_count" => 0, + "id" => ^other_user_id + }, + "state" => "follow_accept" + } = Jason.decode!(payload) + end end describe "public streams" do @@ -563,7 +624,7 @@ defmodule Pleroma.Web.StreamerTest do user_b = insert(:user) user_c = insert(:user) - {:ok, user_a} = User.follow(user_a, user_b) + {:ok, user_a, user_b} = User.follow(user_a, user_b) {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) @@ -599,7 +660,7 @@ defmodule Pleroma.Web.StreamerTest do test "it sends wanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) - {:ok, user_a} = User.follow(user_a, user_b) + {:ok, user_a, user_b} = User.follow(user_a, user_b) {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) diff --git a/test/pleroma/web/twitter_api/twitter_api_test.exs b/test/pleroma/web/twitter_api/twitter_api_test.exs index 20a45cb6f..4a418dee3 100644 --- a/test/pleroma/web/twitter_api/twitter_api_test.exs +++ b/test/pleroma/web/twitter_api/twitter_api_test.exs @@ -80,13 +80,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do end test "it sends an admin email if :account_approval_required is specified in instance config" do - admin = insert(:user, is_admin: true) - setting = Pleroma.Config.get([:instance, :account_approval_required]) + clear_config([:instance, :account_approval_required], true) - unless setting do - Pleroma.Config.put([:instance, :account_approval_required], true) - on_exit(fn -> Pleroma.Config.put([:instance, :account_approval_required], setting) end) - end + admin = insert(:user, is_admin: true) data = %{ :username => "lain", @@ -103,15 +99,24 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do assert user.approval_pending - email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user) + user_email = Pleroma.Emails.UserEmail.approval_pending_email(user) + admin_email = Pleroma.Emails.AdminEmail.new_unapproved_registration(admin, user) notify_email = Pleroma.Config.get([:instance, :notify_email]) instance_name = Pleroma.Config.get([:instance, :name]) + # User approval email + Swoosh.TestAssertions.assert_email_sent( + from: {instance_name, notify_email}, + to: {user.name, user.email}, + html_body: user_email.html_body + ) + + # Admin email Swoosh.TestAssertions.assert_email_sent( from: {instance_name, notify_email}, to: {admin.name, admin.email}, - html_body: email.html_body + html_body: admin_email.html_body ) end diff --git a/test/pleroma/workers/cron/new_users_digest_worker_test.exs b/test/pleroma/workers/cron/new_users_digest_worker_test.exs index 129534cb1..75c9aa4a3 100644 --- a/test/pleroma/workers/cron/new_users_digest_worker_test.exs +++ b/test/pleroma/workers/cron/new_users_digest_worker_test.exs @@ -28,7 +28,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorkerTest do assert email.html_body =~ user.nickname assert email.html_body =~ user2.nickname assert email.html_body =~ "cofe" - assert email.html_body =~ "#{Pleroma.Web.Endpoint.url()}/static/logo.png" + assert email.html_body =~ "#{Pleroma.Web.Endpoint.url()}/static/logo.svg" end test "it doesn't fail when admin has no email" do |