From 9ca45063556f3b75860d516577776a00536e90a8 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 1 Aug 2019 15:53:37 +0700 Subject: [PATCH 01/33] Add configurable length limits for `User.bio` and `User.name` --- config/config.exs | 2 ++ docs/config.md | 2 ++ lib/pleroma/user.ex | 38 +++++++++++++++++++++----------------- test/user_test.exs | 5 ++++- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/config/config.exs b/config/config.exs index 17770640a..aa4cdf409 100644 --- a/config/config.exs +++ b/config/config.exs @@ -253,6 +253,8 @@ skip_thread_containment: true, limit_to_local_content: :unauthenticated, dynamic_configuration: false, + user_bio_length: 5000, + user_name_length: 100, external_user_synchronization: true config :pleroma, :markup, diff --git a/docs/config.md b/docs/config.md index 02f86dc16..8f58eaf06 100644 --- a/docs/config.md +++ b/docs/config.md @@ -125,6 +125,8 @@ config :pleroma, Pleroma.Emails.Mailer, * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). Default: `false`. * `healthcheck`: If set to true, system data will be shown on ``/api/pleroma/healthcheck``. * `remote_post_retention_days`: The default amount of days to retain remote posts when pruning the database. +* `user_bio_length`: A user bio maximum length (default: `5000`) +* `user_name_length`: A user name maximum length (default: `100`) * `skip_thread_containment`: Skip filter out broken threads. The default is `false`. * `limit_to_local_content`: Limit unauthenticated users to search for local statutes and users only. Possible values: `:unauthenticated`, `:all` and `false`. The default is `:unauthenticated`. * `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api. diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1adb82f32..776dbbe6d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -149,10 +149,10 @@ def following_count(%User{} = user) do end def remote_user_creation(params) do - params = - params - |> Map.put(:info, params[:info] || %{}) + bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) + name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) + params = Map.put(params, :info, params[:info] || %{}) info_cng = User.Info.remote_user_creation(%User.Info{}, params[:info]) changes = @@ -161,8 +161,8 @@ def remote_user_creation(params) do |> validate_required([:name, :ap_id]) |> unique_constraint(:nickname) |> validate_format(:nickname, @email_regex) - |> validate_length(:bio, max: 5000) - |> validate_length(:name, max: 100) + |> validate_length(:bio, max: bio_limit) + |> validate_length(:name, max: name_limit) |> put_change(:local, false) |> put_embed(:info, info_cng) @@ -185,22 +185,23 @@ def remote_user_creation(params) do end def update_changeset(struct, params \\ %{}) do + bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) + name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) + struct |> cast(params, [:bio, :name, :avatar, :following]) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) - |> validate_length(:bio, max: 5000) - |> validate_length(:name, min: 1, max: 100) + |> validate_length(:bio, max: bio_limit) + |> validate_length(:name, min: 1, max: name_limit) end def upgrade_changeset(struct, params \\ %{}) do - params = - params - |> Map.put(:last_refreshed_at, NaiveDateTime.utc_now()) + bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) + name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) - info_cng = - struct.info - |> User.Info.user_upgrade(params[:info]) + params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now()) + info_cng = User.Info.user_upgrade(struct.info, params[:info]) struct |> cast(params, [ @@ -213,8 +214,8 @@ def upgrade_changeset(struct, params \\ %{}) do ]) |> unique_constraint(:nickname) |> validate_format(:nickname, local_nickname_regex()) - |> validate_length(:bio, max: 5000) - |> validate_length(:name, max: 100) + |> validate_length(:bio, max: bio_limit) + |> validate_length(:name, max: name_limit) |> put_embed(:info, info_cng) end @@ -241,6 +242,9 @@ def reset_password(%User{id: user_id} = user, data) do end def register_changeset(struct, params \\ %{}, opts \\ []) do + bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) + name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) + need_confirmation? = if is_nil(opts[:need_confirmation]) do Pleroma.Config.get([:instance, :account_activation_required]) @@ -261,8 +265,8 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames])) |> validate_format(:nickname, local_nickname_regex()) |> validate_format(:email, @email_regex) - |> validate_length(:bio, max: 1000) - |> validate_length(:name, min: 1, max: 100) + |> validate_length(:bio, max: bio_limit) + |> validate_length(:name, min: 1, max: name_limit) |> put_change(:info, info_change) changeset = diff --git a/test/user_test.exs b/test/user_test.exs index 556df45fd..dfa91a106 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -525,7 +525,10 @@ test "it has required fields" do end test "it restricts some sizes" do - [bio: 5000, name: 100] + bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) + name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) + + [bio: bio_limit, name: name_limit] |> Enum.each(fn {field, size} -> string = String.pad_leading(".", size) cs = User.remote_user_creation(Map.put(@valid_remote, field, string)) From bbd9ed02576f1599e90f8575573fe6e935d32eae Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Mon, 5 Aug 2019 15:33:34 +0700 Subject: [PATCH 02/33] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd64b2259..e9d4e1710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Added synchronization of following/followers counters for external users - Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`. - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options. +- Configuration: `user_bio_length` and `user_name_length` options. - Addressable lists - Twitter API: added rate limit for `/api/account/password_reset` endpoint. - ActivityPub: Add an internal service actor for fetching ActivityPub objects. From 409bcad54b5de631536761952faed05ad5fe3b99 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Fri, 9 Aug 2019 16:49:09 +0300 Subject: [PATCH 03/33] Mastodon API: Set follower/following counters to 0 when hiding followers/following is enabled We are already doing that in AP representation, so I think we should do it here as well for consistency. --- CHANGELOG.md | 1 + .../web/mastodon_api/views/account_view.ex | 11 ++++++-- test/web/mastodon_api/account_view_test.exs | 27 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccc36965..5d08fe757 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Federation/MediaProxy not working with instances that have wrong certificate order - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity +- Mastodon API: follower/following counters not being nullified, when `hide_follows`/`hide_followers` is set - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`) - Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index de084fd6e..72c092f25 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -72,6 +72,13 @@ defp do_render("account.json", %{user: user} = opts) do image = User.avatar_url(user) |> MediaProxy.url() header = User.banner_url(user) |> MediaProxy.url() user_info = User.get_cached_user_info(user) + + following_count = + ((!user.info.hide_follows or opts[:for] == user) && user_info.following_count) || 0 + + followers_count = + ((!user.info.hide_followers or opts[:for] == user) && user_info.follower_count) || 0 + bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"] emojis = @@ -102,8 +109,8 @@ defp do_render("account.json", %{user: user} = opts) do display_name: display_name, locked: user_info.locked, created_at: Utils.to_masto_date(user.inserted_at), - followers_count: user_info.follower_count, - following_count: user_info.following_count, + followers_count: followers_count, + following_count: following_count, statuses_count: user_info.note_count, note: bio || "", url: User.profile_url(user), diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/account_view_test.exs index 905e9af98..a26f514a5 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/account_view_test.exs @@ -356,4 +356,31 @@ test "sanitizes display names" do result = AccountView.render("account.json", %{user: user}) refute result.display_name == " username " end + + describe "hiding follows/following" do + test "shows when follows/following are hidden and sets follower/following count to 0" do + user = insert(:user, info: %{hide_followers: true, hide_follows: true}) + other_user = insert(:user) + {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{ + followers_count: 0, + following_count: 0, + pleroma: %{hide_follows: true, hide_followers: true} + } = AccountView.render("account.json", %{user: user}) + end + + test "shows actual follower/following count to the account owner" do + user = insert(:user, info: %{hide_followers: true, hide_follows: true}) + other_user = insert(:user) + {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + + assert %{ + followers_count: 1, + following_count: 1 + } = AccountView.render("account.json", %{user: user, for: user}) + end + end end From bb9c53958038bb74ad76a9d887b15e6decb5249c Mon Sep 17 00:00:00 2001 From: Maksim Date: Sat, 10 Aug 2019 11:27:59 +0000 Subject: [PATCH 04/33] Uploader.S3 added support stream uploads --- lib/pleroma/uploaders/s3.ex | 12 ++--- mix.exs | 3 +- mix.lock | 1 + test/uploaders/s3_test.exs | 90 +++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 test/uploaders/s3_test.exs diff --git a/lib/pleroma/uploaders/s3.ex b/lib/pleroma/uploaders/s3.ex index 521daa93b..8c353bed3 100644 --- a/lib/pleroma/uploaders/s3.ex +++ b/lib/pleroma/uploaders/s3.ex @@ -6,10 +6,12 @@ defmodule Pleroma.Uploaders.S3 do @behaviour Pleroma.Uploaders.Uploader require Logger + alias Pleroma.Config + # The file name is re-encoded with S3's constraints here to comply with previous # links with less strict filenames def get_file(file) do - config = Pleroma.Config.get([__MODULE__]) + config = Config.get([__MODULE__]) bucket = Keyword.fetch!(config, :bucket) bucket_with_namespace = @@ -34,15 +36,15 @@ def get_file(file) do end def put_file(%Pleroma.Upload{} = upload) do - config = Pleroma.Config.get([__MODULE__]) + config = Config.get([__MODULE__]) bucket = Keyword.get(config, :bucket) - {:ok, file_data} = File.read(upload.tempfile) - s3_name = strict_encode(upload.path) op = - ExAws.S3.put_object(bucket, s3_name, file_data, [ + upload.tempfile + |> ExAws.S3.Upload.stream_file() + |> ExAws.S3.upload(bucket, s3_name, [ {:acl, :public_read}, {:content_type, upload.content_type} ]) diff --git a/mix.exs b/mix.exs index ac175dfed..334fabb33 100644 --- a/mix.exs +++ b/mix.exs @@ -114,8 +114,9 @@ defp deps do {:tesla, "~> 1.2"}, {:jason, "~> 1.0"}, {:mogrify, "~> 0.6.1"}, - {:ex_aws, "~> 2.0"}, + {:ex_aws, "~> 2.1"}, {:ex_aws_s3, "~> 2.0"}, + {:sweet_xml, "~> 0.6.6"}, {:earmark, "~> 1.3"}, {:bbcode, "~> 0.1.1"}, {:ex_machina, "~> 2.3", only: :test}, diff --git a/mix.lock b/mix.lock index 13728d11f..f8ee80c83 100644 --- a/mix.lock +++ b/mix.lock @@ -80,6 +80,7 @@ "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, + "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"}, "swoosh": {:hex, :swoosh, "0.23.2", "7dda95ff0bf54a2298328d6899c74dae1223777b43563ccebebb4b5d2b61df38", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {: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"}, "syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]}, "telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"}, diff --git a/test/uploaders/s3_test.exs b/test/uploaders/s3_test.exs new file mode 100644 index 000000000..a0a1cfdf0 --- /dev/null +++ b/test/uploaders/s3_test.exs @@ -0,0 +1,90 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Uploaders.S3Test do + use Pleroma.DataCase + + alias Pleroma.Config + alias Pleroma.Uploaders.S3 + + import Mock + import ExUnit.CaptureLog + + setup do + config = Config.get([Pleroma.Uploaders.S3]) + + Config.put([Pleroma.Uploaders.S3], + bucket: "test_bucket", + public_endpoint: "https://s3.amazonaws.com" + ) + + on_exit(fn -> + Config.put([Pleroma.Uploaders.S3], config) + end) + + :ok + end + + describe "get_file/1" do + test "it returns path to local folder for files" do + assert S3.get_file("test_image.jpg") == { + :ok, + {:url, "https://s3.amazonaws.com/test_bucket/test_image.jpg"} + } + end + + test "it returns path without bucket when truncated_namespace set to ''" do + Config.put([Pleroma.Uploaders.S3], + bucket: "test_bucket", + public_endpoint: "https://s3.amazonaws.com", + truncated_namespace: "" + ) + + assert S3.get_file("test_image.jpg") == { + :ok, + {:url, "https://s3.amazonaws.com/test_image.jpg"} + } + end + + test "it returns path with bucket namespace when namespace is set" do + Config.put([Pleroma.Uploaders.S3], + bucket: "test_bucket", + public_endpoint: "https://s3.amazonaws.com", + bucket_namespace: "family" + ) + + assert S3.get_file("test_image.jpg") == { + :ok, + {:url, "https://s3.amazonaws.com/family:test_bucket/test_image.jpg"} + } + end + end + + describe "put_file/1" do + setup do + file_upload = %Pleroma.Upload{ + name: "image-tet.jpg", + content_type: "image/jpg", + path: "test_folder/image-tet.jpg", + tempfile: Path.absname("test/fixtures/image_tmp.jpg") + } + + [file_upload: file_upload] + end + + test "save file", %{file_upload: file_upload} do + with_mock ExAws, request: fn _ -> {:ok, :ok} end do + assert S3.put_file(file_upload) == {:ok, {:file, "test_folder/image-tet.jpg"}} + end + end + + test "returns error", %{file_upload: file_upload} do + with_mock ExAws, request: fn _ -> {:error, "S3 Upload failed"} end do + assert capture_log(fn -> + assert S3.put_file(file_upload) == {:error, "S3 Upload failed"} + end) =~ "Elixir.Pleroma.Uploaders.S3: {:error, \"S3 Upload failed\"}" + end + end + end +end From 0802a08871afee7f09362cbca8b802f0e27ff4b9 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sat, 10 Aug 2019 16:27:46 +0300 Subject: [PATCH 05/33] Mastodon API: Fix thread mute detection It was calling CommonAPI.thread_muted? with post author's account instead of viewer's one. --- CHANGELOG.md | 1 + lib/pleroma/web/mastodon_api/views/status_view.ex | 2 +- test/web/mastodon_api/mastodon_api_controller_test.exs | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dccc36965..31caef499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Federation/MediaProxy not working with instances that have wrong certificate order - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity +- Mastodon API: `muted` in the Status entity, using author's account to determine if the tread was muted - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`) - Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 80df9b2ac..02819e116 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -168,7 +168,7 @@ def render("status.json", %{activity: %{data: %{"object" => _object}} = activity thread_muted? = case activity.thread_muted? do thread_muted? when is_boolean(thread_muted?) -> thread_muted? - nil -> CommonAPI.thread_muted?(user, activity) + nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false end attachment_data = object.data["attachment"] || [] diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index e49c4cc22..b023d1e4f 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -2901,8 +2901,10 @@ test "bookmarks" do describe "conversation muting" do setup do + post_user = insert(:user) user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"}) + + {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) [user: user, activity: activity] end From 11d08c2de0226caed8119bb3a45a8e0ab8791fbe Mon Sep 17 00:00:00 2001 From: Maksim Date: Sat, 10 Aug 2019 18:46:26 +0000 Subject: [PATCH 06/33] tests for Pleroma.Uploaders --- docs/config.md | 1 + lib/pleroma/uploaders/local.ex | 4 +-- lib/pleroma/uploaders/mdii.ex | 2 ++ test/uploaders/local_test.exs | 32 ++++++++++++++++++++++ test/uploaders/mdii_test.exs | 50 ++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 test/uploaders/local_test.exs create mode 100644 test/uploaders/mdii_test.exs diff --git a/docs/config.md b/docs/config.md index 703ef67dd..55311b76d 100644 --- a/docs/config.md +++ b/docs/config.md @@ -18,6 +18,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. ## Pleroma.Uploaders.S3 * `bucket`: S3 bucket name +* `bucket_namespace`: S3 bucket namespace * `public_endpoint`: S3 endpoint that the user finally accesses(ex. "https://s3.dualstack.ap-northeast-1.amazonaws.com") * `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc. For example, when using CDN to S3 virtual host format, set "". diff --git a/lib/pleroma/uploaders/local.ex b/lib/pleroma/uploaders/local.ex index fc533da23..36b3c35ec 100644 --- a/lib/pleroma/uploaders/local.ex +++ b/lib/pleroma/uploaders/local.ex @@ -11,7 +11,7 @@ def get_file(_) do def put_file(upload) do {local_path, file} = - case Enum.reverse(String.split(upload.path, "/", trim: true)) do + case Enum.reverse(Path.split(upload.path)) do [file] -> {upload_path(), file} @@ -23,7 +23,7 @@ def put_file(upload) do result_file = Path.join(local_path, file) - unless File.exists?(result_file) do + if not File.exists?(result_file) do File.cp!(upload.tempfile, result_file) end diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 237544337..c36f3d61d 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -3,6 +3,8 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Uploaders.MDII do + @moduledoc "Represents uploader for https://github.com/hakaba-hitoyo/minimal-digital-image-infrastructure" + alias Pleroma.Config alias Pleroma.HTTP diff --git a/test/uploaders/local_test.exs b/test/uploaders/local_test.exs new file mode 100644 index 000000000..fc442d0f1 --- /dev/null +++ b/test/uploaders/local_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Uploaders.LocalTest do + use Pleroma.DataCase + alias Pleroma.Uploaders.Local + + describe "get_file/1" do + test "it returns path to local folder for files" do + assert Local.get_file("") == {:ok, {:static_dir, "test/uploads"}} + end + end + + describe "put_file/1" do + test "put file to local folder" do + file_path = "local_upload/files/image.jpg" + + file = %Pleroma.Upload{ + name: "image.jpg", + content_type: "image/jpg", + path: file_path, + tempfile: Path.absname("test/fixtures/image_tmp.jpg") + } + + assert Local.put_file(file) == :ok + + assert Path.join([Local.upload_path(), file_path]) + |> File.exists?() + end + end +end diff --git a/test/uploaders/mdii_test.exs b/test/uploaders/mdii_test.exs new file mode 100644 index 000000000..d432d40f0 --- /dev/null +++ b/test/uploaders/mdii_test.exs @@ -0,0 +1,50 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Uploaders.MDIITest do + use Pleroma.DataCase + alias Pleroma.Uploaders.MDII + import Tesla.Mock + + describe "get_file/1" do + test "it returns path to local folder for files" do + assert MDII.get_file("") == {:ok, {:static_dir, "test/uploads"}} + end + end + + describe "put_file/1" do + setup do + file_upload = %Pleroma.Upload{ + name: "mdii-image.jpg", + content_type: "image/jpg", + path: "test_folder/mdii-image.jpg", + tempfile: Path.absname("test/fixtures/image_tmp.jpg") + } + + [file_upload: file_upload] + end + + test "save file", %{file_upload: file_upload} do + mock(fn + %{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} -> + %Tesla.Env{status: 200, body: "mdii-image"} + end) + + assert MDII.put_file(file_upload) == + {:ok, {:url, "https://mdii.sakura.ne.jp/mdii-image.jpg"}} + end + + test "save file to local if MDII isn`t available", %{file_upload: file_upload} do + mock(fn + %{method: :post, url: "https://mdii.sakura.ne.jp/mdii-post.cgi?jpg"} -> + %Tesla.Env{status: 500} + end) + + assert MDII.put_file(file_upload) == :ok + + assert Path.join([Pleroma.Uploaders.Local.upload_path(), file_upload.path]) + |> File.exists?() + end + end +end From af4cf35e2096a6d1660271f6935b6b9ce77c6757 Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Sat, 10 Aug 2019 18:47:40 +0000 Subject: [PATCH 07/33] Strip internal fields including likes from incoming and outgoing activities --- CHANGELOG.md | 2 ++ lib/mix/tasks/pleroma/database.ex | 36 +++++++++++++++++++ .../web/activity_pub/transmogrifier.ex | 34 ++---------------- test/tasks/database_test.exs | 36 +++++++++++++++++++ test/web/activity_pub/transmogrifier_test.exs | 30 +++++++++++----- 5 files changed, 98 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31caef499..759779034 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Admin API: Endpoint for fetching latest user's statuses - Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=` for resending account confirmation. - Relays: Added a task to list relay subscriptions. +- Mix Tasks: `mix pleroma.database fix_likes_collections` +- Federation: Remove `likes` from objects. ### Changed - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 8547a329a..bcc2052d6 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -36,6 +36,10 @@ defmodule Mix.Tasks.Pleroma.Database do ## Remove duplicated items from following and update followers count for all users mix pleroma.database update_users_following_followers_counts + + ## Fix the pre-existing "likes" collections for all objects + + mix pleroma.database fix_likes_collections """ def run(["remove_embedded_objects" | args]) do {options, [], []} = @@ -125,4 +129,36 @@ def run(["prune_objects" | args]) do ) end end + + def run(["fix_likes_collections"]) do + import Ecto.Query + + start_pleroma() + + from(object in Object, + where: fragment("(?)->>'likes' is not null", object.data), + select: %{id: object.id, likes: fragment("(?)->>'likes'", object.data)} + ) + |> Pleroma.RepoStreamer.chunk_stream(100) + |> Stream.each(fn objects -> + ids = + objects + |> Enum.filter(fn object -> object.likes |> Jason.decode!() |> is_map() end) + |> Enum.map(& &1.id) + + Object + |> where([object], object.id in ^ids) + |> update([object], + set: [ + data: + fragment( + "jsonb_set(?, '{likes}', '[]'::jsonb, true)", + object.data + ) + ] + ) + |> Repo.update_all([], timeout: :infinity) + end) + |> Stream.run() + end end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 5403b71d8..b7bc48f0a 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -26,6 +26,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do """ def fix_object(object, options \\ []) do object + |> strip_internal_fields |> fix_actor |> fix_url |> fix_attachments @@ -34,7 +35,6 @@ def fix_object(object, options \\ []) do |> fix_emoji |> fix_tag |> fix_content_map - |> fix_likes |> fix_addressing |> fix_summary |> fix_type(options) @@ -151,20 +151,6 @@ def fix_actor(%{"attributedTo" => actor} = object) do |> Map.put("actor", Containment.get_actor(%{"actor" => actor})) end - # Check for standardisation - # This is what Peertube does - # curl -H 'Accept: application/activity+json' $likes | jq .totalItems - # Prismo returns only an integer (count) as "likes" - def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do - object - |> Map.put("likes", []) - |> Map.put("like_count", 0) - end - - def fix_likes(object) do - object - end - def fix_in_reply_to(object, options \\ []) def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options) @@ -784,7 +770,6 @@ def prepare_object(object) do |> add_mention_tags |> add_emoji_tags |> add_attributed_to - |> add_likes |> prepare_attachments |> set_conversation |> set_reply_to_uri @@ -971,22 +956,6 @@ def add_attributed_to(object) do |> Map.put("attributedTo", attributed_to) end - def add_likes(%{"id" => id, "like_count" => likes} = object) do - likes = %{ - "id" => "#{id}/likes", - "first" => "#{id}/likes?page=1", - "type" => "OrderedCollection", - "totalItems" => likes - } - - object - |> Map.put("likes", likes) - end - - def add_likes(object) do - object - end - def prepare_attachments(object) do attachments = (object["attachment"] || []) @@ -1002,6 +971,7 @@ def prepare_attachments(object) do defp strip_internal_fields(object) do object |> Map.drop([ + "likes", "like_count", "announcements", "announcement_count", diff --git a/test/tasks/database_test.exs b/test/tasks/database_test.exs index 579130b05..a8f25f500 100644 --- a/test/tasks/database_test.exs +++ b/test/tasks/database_test.exs @@ -3,8 +3,11 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.DatabaseTest do + alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.CommonAPI + use Pleroma.DataCase import Pleroma.Factory @@ -46,4 +49,37 @@ test "following and followers count are updated" do assert user.info.follower_count == 0 end end + + describe "running fix_likes_collections" do + test "it turns OrderedCollection likes into empty arrays" do + [user, user2] = insert_pair(:user) + + {:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"}) + {:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"}) + + CommonAPI.favorite(id, user2) + + likes = %{ + "first" => + "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1", + "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes", + "totalItems" => 3, + "type" => "OrderedCollection" + } + + new_data = Map.put(object2.data, "likes", likes) + + object2 + |> Ecto.Changeset.change(%{data: new_data}) + |> Repo.update() + + assert length(Object.get_by_id(object.id).data["likes"]) == 1 + assert is_map(Object.get_by_id(object2.id).data["likes"]) + + assert :ok == Mix.Tasks.Pleroma.Database.run(["fix_likes_collections"]) + + assert length(Object.get_by_id(object.id).data["likes"]) == 1 + assert Enum.empty?(Object.get_by_id(object2.id).data["likes"]) + end + end end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index e7498e005..060b91e29 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -450,6 +450,27 @@ test "it ensures that address fields become lists" do assert !is_nil(data["cc"]) end + test "it strips internal likes" do + data = + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + + likes = %{ + "first" => + "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1", + "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes", + "totalItems" => 3, + "type" => "OrderedCollection" + } + + object = Map.put(data["object"], "likes", likes) + data = Map.put(data, "object", object) + + {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data) + + refute Map.has_key?(object.data, "likes") + end + test "it works for incoming update activities" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -1061,14 +1082,7 @@ test "it strips internal fields of article" do assert is_nil(modified["object"]["announcements"]) assert is_nil(modified["object"]["announcement_count"]) assert is_nil(modified["object"]["context_id"]) - end - - test "it adds like collection to object" do - activity = insert(:note_activity) - {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - - assert modified["object"]["likes"]["type"] == "OrderedCollection" - assert modified["object"]["likes"]["totalItems"] == 0 + assert is_nil(modified["object"]["likes"]) end test "the directMessage flag is present" do From 9cfc289594c1d2a1b53c99e3e72bba4b6dc615ca Mon Sep 17 00:00:00 2001 From: Ariadne Conill Date: Sat, 10 Aug 2019 21:18:26 +0000 Subject: [PATCH 08/33] MRF: ensure that subdomain_match calls are case-insensitive --- CHANGELOG.md | 1 + lib/pleroma/web/activity_pub/mrf.ex | 2 +- test/web/activity_pub/mrf/mrf_test.exs | 24 +++++++++++++++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfc73c8df..6f1a22359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Invalid SemVer version generation, when the current branch does not have commits ahead of tag/checked out on a tag - Pleroma.Upload base_url was not automatically whitelisted by MediaProxy. Now your custom CDN or file hosting will be accessed directly as expected. - Report email not being sent to admins when the reporter is a remote user +- MRF: ensure that subdomain_match calls are case-insensitive ### Added - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index dd204b21c..caa2a3231 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -28,7 +28,7 @@ defp get_policies(_), do: [] @spec subdomains_regex([String.t()]) :: [Regex.t()] def subdomains_regex(domains) when is_list(domains) do - for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$) + for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)i end @spec subdomain_match?([Regex.t()], String.t()) :: boolean() diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs index a9cdf5317..1a888e18f 100644 --- a/test/web/activity_pub/mrf/mrf_test.exs +++ b/test/web/activity_pub/mrf/mrf_test.exs @@ -4,8 +4,8 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "subdomains_regex/1" do assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ - ~r/^unsafe.tld$/, - ~r/^(.*\.)*unsafe.tld$/ + ~r/^unsafe.tld$/i, + ~r/^(.*\.)*unsafe.tld$/i ] end @@ -13,7 +13,7 @@ test "subdomains_regex/1" do test "common domains" do regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) - assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/] + assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i] assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe2.tld") @@ -24,7 +24,7 @@ test "common domains" do test "wildcard domains with one subdomain" do regexes = MRF.subdomains_regex(["*.unsafe.tld"]) - assert regexes == [~r/^(.*\.)*unsafe.tld$/] + assert regexes == [~r/^(.*\.)*unsafe.tld$/i] assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") @@ -35,12 +35,26 @@ test "wildcard domains with one subdomain" do test "wildcard domains with two subdomains" do regexes = MRF.subdomains_regex(["*.unsafe.tld"]) - assert regexes == [~r/^(.*\.)*unsafe.tld$/] + assert regexes == [~r/^(.*\.)*unsafe.tld$/i] assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld") refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother") end + + test "matches are case-insensitive" do + regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"]) + + assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i] + + assert MRF.subdomain_match?(regexes, "UNSAFE.TLD") + assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD") + assert MRF.subdomain_match?(regexes, "unsafe.tld") + assert MRF.subdomain_match?(regexes, "unsafe2.tld") + + refute MRF.subdomain_match?(regexes, "EXAMPLE.COM") + refute MRF.subdomain_match?(regexes, "example.com") + end end end From 92479c6f4870f1ebe4f530db6e31ba960855e1fa Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 11 Aug 2019 22:49:55 +0300 Subject: [PATCH 09/33] Do not fetch the reply object in `fix_type` unless the object has the `name` key and use a depth limit when fetching it --- lib/pleroma/web/activity_pub/transmogrifier.ex | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index b7bc48f0a..0aee9369f 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -333,13 +333,15 @@ def fix_content_map(object), do: object def fix_type(object, options \\ []) - def fix_type(%{"inReplyTo" => reply_id} = object, options) when is_binary(reply_id) do + def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) + when is_binary(reply_id) do reply = - if Federator.allowed_incoming_reply_depth?(options[:depth]) do - Object.normalize(reply_id, true) + with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), + {:ok, object} <- get_obj_helper(reply_id, options) do + object end - if reply && (reply.data["type"] == "Question" and object["name"]) do + if reply && reply.data["type"] == "Question" do Map.put(object, "type", "Answer") else object From d4d31ffdc4ea1b7a1bb154dbf6a61a90b99c646e Mon Sep 17 00:00:00 2001 From: rinpatch Date: Sun, 11 Aug 2019 23:19:20 +0300 Subject: [PATCH 10/33] Add a changelog entry for !1552 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f1a22359..f3338a5b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Not being able to pin unlisted posts - Objects being re-embedded to activities after being updated (e.g faved/reposted). Running 'mix pleroma.database prune_objects' again is advised. - Metadata rendering errors resulting in the entire page being inaccessible +- `federation_incoming_replies_max_depth` option being ignored in certain cases - Federation/MediaProxy not working with instances that have wrong certificate order - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity From 24a731a9a67a719749c99ca925f4adb2973a3f2d Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Mon, 12 Aug 2019 15:00:03 -0500 Subject: [PATCH 11/33] Update AdminFE Now permits server configuration. Consider this ALPHA. --- priv/static/adminfe/chunk-0e18.e12401fb.css | Bin 0 -> 723 bytes ...9.c27dac5e.css => chunk-1fbf.d7a1893c.css} | Bin 3624 -> 3624 bytes ...8.0d22684d.css => chunk-2325.0d22684d.css} | Bin priv/static/adminfe/chunk-5e57.ac97b15a.css | Bin 0 -> 2321 bytes ...f.1a04e979.css => chunk-e547.e4b6230b.css} | Bin 3304 -> 3304 bytes .../adminfe/chunk-elementUI.e5cd8da6.css | Bin 0 -> 224642 bytes .../adminfe/chunk-elementUI.f74c256b.css | Bin 202027 -> 0 bytes priv/static/adminfe/index.html | 2 +- .../static/fonts/element-icons.2fad952.woff | Bin 6164 -> 0 bytes .../static/fonts/element-icons.535877f.woff | Bin 0 -> 28200 bytes .../static/fonts/element-icons.6f0a763.ttf | Bin 11040 -> 0 bytes .../static/fonts/element-icons.732389d.ttf | Bin 0 -> 55956 bytes priv/static/adminfe/static/js/app.4137ad8f.js | Bin 115467 -> 0 bytes priv/static/adminfe/static/js/app.8e186193.js | Bin 0 -> 137815 bytes .../adminfe/static/js/chunk-0e18.208cd826.js | Bin 0 -> 4774 bytes .../adminfe/static/js/chunk-1fbf.616fb309.js | Bin 0 -> 17717 bytes ...018.e1a7a454.js => chunk-2325.154a537b.js} | Bin 8220 -> 8220 bytes .../adminfe/static/js/chunk-56c9.28e35fc3.js | Bin 14105 -> 0 bytes .../adminfe/static/js/chunk-5e57.7313703a.js | Bin 0 -> 217441 bytes .../adminfe/static/js/chunk-5eaf.5b76e416.js | Bin 23071 -> 0 bytes .../adminfe/static/js/chunk-7fe2.458f9da5.js | Bin 0 -> 408401 bytes .../adminfe/static/js/chunk-e547.d57d1b91.js | Bin 0 -> 23125 bytes .../static/js/chunk-elementUI.1911151b.js | Bin 0 -> 638883 bytes .../static/js/chunk-elementUI.1fa5434b.js | Bin 562077 -> 0 bytes ...ibs.d5609760.js => chunk-libs.fb0b7f4a.js} | Bin 204098 -> 204635 bytes .../adminfe/static/js/runtime.d8d12c12.js | Bin 3434 -> 0 bytes .../adminfe/static/js/runtime.f40c8ec4.js | Bin 0 -> 3608 bytes 27 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 priv/static/adminfe/chunk-0e18.e12401fb.css rename priv/static/adminfe/{chunk-56c9.c27dac5e.css => chunk-1fbf.d7a1893c.css} (95%) rename priv/static/adminfe/{chunk-f018.0d22684d.css => chunk-2325.0d22684d.css} (100%) create mode 100644 priv/static/adminfe/chunk-5e57.ac97b15a.css rename priv/static/adminfe/{chunk-5eaf.1a04e979.css => chunk-e547.e4b6230b.css} (60%) create mode 100644 priv/static/adminfe/chunk-elementUI.e5cd8da6.css delete mode 100644 priv/static/adminfe/chunk-elementUI.f74c256b.css delete mode 100644 priv/static/adminfe/static/fonts/element-icons.2fad952.woff create mode 100644 priv/static/adminfe/static/fonts/element-icons.535877f.woff delete mode 100644 priv/static/adminfe/static/fonts/element-icons.6f0a763.ttf create mode 100644 priv/static/adminfe/static/fonts/element-icons.732389d.ttf delete mode 100644 priv/static/adminfe/static/js/app.4137ad8f.js create mode 100644 priv/static/adminfe/static/js/app.8e186193.js create mode 100644 priv/static/adminfe/static/js/chunk-0e18.208cd826.js create mode 100644 priv/static/adminfe/static/js/chunk-1fbf.616fb309.js rename priv/static/adminfe/static/js/{chunk-f018.e1a7a454.js => chunk-2325.154a537b.js} (98%) delete mode 100644 priv/static/adminfe/static/js/chunk-56c9.28e35fc3.js create mode 100644 priv/static/adminfe/static/js/chunk-5e57.7313703a.js delete mode 100644 priv/static/adminfe/static/js/chunk-5eaf.5b76e416.js create mode 100644 priv/static/adminfe/static/js/chunk-7fe2.458f9da5.js create mode 100644 priv/static/adminfe/static/js/chunk-e547.d57d1b91.js create mode 100644 priv/static/adminfe/static/js/chunk-elementUI.1911151b.js delete mode 100644 priv/static/adminfe/static/js/chunk-elementUI.1fa5434b.js rename priv/static/adminfe/static/js/{chunk-libs.d5609760.js => chunk-libs.fb0b7f4a.js} (71%) delete mode 100644 priv/static/adminfe/static/js/runtime.d8d12c12.js create mode 100644 priv/static/adminfe/static/js/runtime.f40c8ec4.js diff --git a/priv/static/adminfe/chunk-0e18.e12401fb.css b/priv/static/adminfe/chunk-0e18.e12401fb.css new file mode 100644 index 0000000000000000000000000000000000000000..ba85e77d555e97cbaf09bdaef884580de5d557c3 GIT binary patch literal 723 zcmZ{iL2iRE5Jj)Trn?R@RBa@4l&XsX4`9{UmOV*`qTIchKokWkcB40s?{D{nio&-- zMmWKtXby^$__@NF>R-)JyAjan&dP=?Q>b8w&>DJ~&Io9xA+Dg((Hp$TCsXy9Et1Lp zm?dd7VCb}!W$DLER34SmwgW>g%i`0Iw|0V+;(iK|#5oz57hviq?DZ$HoyvND0=wXjexrTmwm?m-3v{iq`ArI|Qal$V z2ei>+m~Q2kduL2`+(~V8WQcq*1bp!%t+TY&Dn)fa)Q5Px<$Azwr>r|sK8Q>Y-6rI9 zDMutMGV(D}+*0dx2Ho{6%el$eyEFKpPslreXBv5Ve)Cdgv?b_i7JME2xSj=`oPWX$ B0WSam literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/chunk-56c9.c27dac5e.css b/priv/static/adminfe/chunk-1fbf.d7a1893c.css similarity index 95% rename from priv/static/adminfe/chunk-56c9.c27dac5e.css rename to priv/static/adminfe/chunk-1fbf.d7a1893c.css index 2b4283ec5479d85daee882406947355c6599c6c9..4672a9f758af89f89f69ce05a0216d8cece66601 100644 GIT binary patch delta 34 icmZ1>vqEMAFE@vQxtXPDQnJZpQSN3qXY&H?4kiGlSqX;# delta 34 icmZ1>vqEMAFE>Y0l8LdUK}zCeQSN3qXY&H?4kiGxbP6#5 diff --git a/priv/static/adminfe/chunk-f018.0d22684d.css b/priv/static/adminfe/chunk-2325.0d22684d.css similarity index 100% rename from priv/static/adminfe/chunk-f018.0d22684d.css rename to priv/static/adminfe/chunk-2325.0d22684d.css diff --git a/priv/static/adminfe/chunk-5e57.ac97b15a.css b/priv/static/adminfe/chunk-5e57.ac97b15a.css new file mode 100644 index 0000000000000000000000000000000000000000..0c9284744e242456ea5327b6682c1ec4f8abe280 GIT binary patch literal 2321 zcmd6pTWjM+6oCJVAS}e-5t8lLS?Q&;u$z~b!akK{F(c_%9x|E{GviAt{NH=#B1?*s z-7WN?J7Km=xJTRG>H$aEVJtbz zsv*ok(=}ApOc2HDy~&nI_^yFNX}S}u%sxw#^9d7k*S=_rtvt#SAnL1dAC%(~&~n%rWN69yh?!^)J=@jehaL(7i7c2$FDYPzKMxR-Q1dKB zK`l9}qB{TxCNNcG(PS3&C2puLr5S+<@dTk1eO-loNK=8$=)0f|j4WMo{u{(Uus#XF zx1)#Ve28k7()09Vnk=yUW^*a{OQcH$AO^`^ zAHmJK?_3Jc5if}=({{^*UA@Dham!_VXtrBU3*ecM<~|mZ7}*lvE3;mWxZ2vg5$yCe zvsIjwbvPx~PUbchI&1Jo(Hc6-!zY4`Q}M9>5z5Zs6bR@Acr%F|g}uGDr^RfUIk!X6}~*E$|8)4CI!>;sLY0ZKl`O z(b&|w-Jh2@YzIAzMPwVmpo^~+HIy95X7TZ1xhmmX7ou}s9fUWu_vw3j?n2pL0FmpX Fe*ifSS-=1Q literal 0 HcmV?d00001 diff --git a/priv/static/adminfe/chunk-5eaf.1a04e979.css b/priv/static/adminfe/chunk-e547.e4b6230b.css similarity index 60% rename from priv/static/adminfe/chunk-5eaf.1a04e979.css rename to priv/static/adminfe/chunk-e547.e4b6230b.css index a09287f584eb4329d6eefeee36f3424ec8977ee1..f740543a0ae4961dea98604533843ffc08f9f480 100644 GIT binary patch delta 321 zcmaDM`9g9-3JXWFsbO-8MQZBg92Qpu$Cle}@^_Y92!WlfP6!S&nkS zaL%wHIPvjDvT*1RLc`pyY3|zRnATcE+ SGcR2?CpE3a%A%m6wiW>NkY=#} delta 321 zcmaDM`9g9-3JZsET3SjkS zaL%wHIPvjDvT*1RLc`pyY3|zRnATcE+ SGcR2?CpE3a%A%m6wiW=aoMt5e diff --git a/priv/static/adminfe/chunk-elementUI.e5cd8da6.css b/priv/static/adminfe/chunk-elementUI.e5cd8da6.css new file mode 100644 index 0000000000000000000000000000000000000000..3fef5e5fdb26a8762d4ca7ef8be2410ec15c1654 GIT binary patch literal 224642 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 rY}3kDkPzH8JxDaf5|H9;_9$s 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