1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
6 use Pleroma.Web.ConnCase, async: true
8 import ExUnit.CaptureLog
12 alias Pleroma.ConfigDB
15 admin = insert(:user, is_admin: true)
16 token = insert(:oauth_admin_token, user: admin)
20 |> assign(:user, admin)
21 |> assign(:token, token)
23 {:ok, %{admin: admin, token: token, conn: conn}}
26 describe "GET /api/pleroma/admin/config" do
27 setup do: clear_config(:configurable_from_database, true)
29 test "when configuration from database is off", %{conn: conn} do
30 Config.put(:configurable_from_database, false)
31 conn = get(conn, "/api/pleroma/admin/config")
33 assert json_response(conn, 400) ==
35 "error" => "To use this endpoint you need to enable configuration from database."
39 test "with settings only in db", %{conn: conn} do
40 config1 = insert(:config)
41 config2 = insert(:config)
43 conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
48 "group" => ":pleroma",
53 "group" => ":pleroma",
58 } = json_response(conn, 200)
60 assert key1 == config1.key
61 assert key2 == config2.key
64 test "db is added to settings that are in db", %{conn: conn} do
65 _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
67 %{"configs" => configs} =
69 |> get("/api/pleroma/admin/config")
73 Enum.filter(configs, fn %{"group" => group, "key" => key} ->
74 group == ":pleroma" and key == ":instance"
77 assert instance_config["db"] == [":name"]
80 test "merged default setting with db settings", %{conn: conn} do
81 config1 = insert(:config)
82 config2 = insert(:config)
86 value: ConfigDB.to_binary(k1: :v1, k2: :v2)
89 %{"configs" => configs} =
91 |> get("/api/pleroma/admin/config")
94 assert length(configs) > 3
97 Enum.filter(configs, fn %{"group" => group, "key" => key} ->
98 group == ":pleroma" and key in [config1.key, config2.key, config3.key]
101 assert length(received_configs) == 3
105 |> ConfigDB.from_binary()
107 |> ConfigDB.convert()
109 Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
110 assert db in [[config1.key], [config2.key], db_keys]
113 ConfigDB.from_binary_with_convert(config1.value),
114 ConfigDB.from_binary_with_convert(config2.value),
115 ConfigDB.from_binary_with_convert(config3.value)
120 test "subkeys with full update right merge", %{conn: conn} do
124 value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
130 value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
133 %{"configs" => configs} =
135 |> get("/api/pleroma/admin/config")
136 |> json_response(200)
139 Enum.filter(configs, fn %{"group" => group, "key" => key} ->
140 group == ":pleroma" and key in [config1.key, config2.key]
143 emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
144 assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
146 emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
147 assets_val = ConfigDB.transform_with_out_binary(assets["value"])
149 assert emoji_val[:groups] == [a: 1, b: 2]
150 assert assets_val[:mascots] == [a: 1, b: 2]
154 test "POST /api/pleroma/admin/config error", %{conn: conn} do
155 conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
157 assert json_response(conn, 400) ==
158 %{"error" => "To use this endpoint you need to enable configuration from database."}
161 describe "POST /api/pleroma/admin/config" do
163 http = Application.get_env(:pleroma, :http)
166 Application.delete_env(:pleroma, :key1)
167 Application.delete_env(:pleroma, :key2)
168 Application.delete_env(:pleroma, :key3)
169 Application.delete_env(:pleroma, :key4)
170 Application.delete_env(:pleroma, :keyaa1)
171 Application.delete_env(:pleroma, :keyaa2)
172 Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
173 Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
174 Application.put_env(:pleroma, :http, http)
175 Application.put_env(:tesla, :adapter, Tesla.Mock)
176 Restarter.Pleroma.refresh()
180 setup do: clear_config(:configurable_from_database, true)
182 @tag capture_log: true
183 test "create new config setting in db", %{conn: conn} do
184 ueberauth = Application.get_env(:ueberauth, Ueberauth)
185 on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
188 post(conn, "/api/pleroma/admin/config", %{
190 %{group: ":pleroma", key: ":key1", value: "value1"},
194 value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
200 ":nested_1" => "nested_value1",
202 %{":nested_22" => "nested_value222"},
203 %{":nested_33" => %{":nested_44" => "nested_444"}}
211 %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
212 %{"nested_4" => true}
218 value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
223 value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
228 assert json_response(conn, 200) == %{
231 "group" => ":pleroma",
237 "group" => ":ueberauth",
238 "key" => "Ueberauth",
239 "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
240 "db" => [":consumer_secret"]
243 "group" => ":pleroma",
246 ":nested_1" => "nested_value1",
248 %{":nested_22" => "nested_value222"},
249 %{":nested_33" => %{":nested_44" => "nested_444"}}
255 "group" => ":pleroma",
258 %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
259 %{"nested_4" => true}
264 "group" => ":pleroma",
266 "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
272 "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
278 assert Application.get_env(:pleroma, :key1) == "value1"
280 assert Application.get_env(:pleroma, :key2) == %{
281 nested_1: "nested_value1",
283 %{nested_22: "nested_value222"},
284 %{nested_33: %{nested_44: "nested_444"}}
288 assert Application.get_env(:pleroma, :key3) == [
289 %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
290 %{"nested_4" => true}
293 assert Application.get_env(:pleroma, :key4) == %{
294 "endpoint" => "https://example.com",
298 assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
301 test "save configs setting without explicit key", %{conn: conn} do
302 level = Application.get_env(:quack, :level)
303 meta = Application.get_env(:quack, :meta)
304 webhook_url = Application.get_env(:quack, :webhook_url)
307 Application.put_env(:quack, :level, level)
308 Application.put_env(:quack, :meta, meta)
309 Application.put_env(:quack, :webhook_url, webhook_url)
313 post(conn, "/api/pleroma/admin/config", %{
328 value: "https://hooks.slack.com/services/KEY"
333 assert json_response(conn, 200) == %{
344 "value" => [":none"],
349 "key" => ":webhook_url",
350 "value" => "https://hooks.slack.com/services/KEY",
351 "db" => [":webhook_url"]
356 assert Application.get_env(:quack, :level) == :info
357 assert Application.get_env(:quack, :meta) == [:none]
358 assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
361 test "saving config with partial update", %{conn: conn} do
362 config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
365 post(conn, "/api/pleroma/admin/config", %{
367 %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
371 assert json_response(conn, 200) == %{
374 "group" => ":pleroma",
377 %{"tuple" => [":key1", 1]},
378 %{"tuple" => [":key2", 2]},
379 %{"tuple" => [":key3", 3]}
381 "db" => [":key1", ":key2", ":key3"]
387 test "saving config which need pleroma reboot", %{conn: conn} do
388 chat = Config.get(:chat)
389 on_exit(fn -> Config.put(:chat, chat) end)
393 "/api/pleroma/admin/config",
396 %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
400 |> json_response(200) == %{
403 "db" => [":enabled"],
404 "group" => ":pleroma",
406 "value" => [%{"tuple" => [":enabled", true]}]
409 "need_reboot" => true
414 |> get("/api/pleroma/admin/config")
415 |> json_response(200)
417 assert configs["need_reboot"]
420 assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
421 end) =~ "pleroma restarted"
425 |> get("/api/pleroma/admin/config")
426 |> json_response(200)
428 assert configs["need_reboot"] == false
431 test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
432 chat = Config.get(:chat)
433 on_exit(fn -> Config.put(:chat, chat) end)
437 "/api/pleroma/admin/config",
440 %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
444 |> json_response(200) == %{
447 "db" => [":enabled"],
448 "group" => ":pleroma",
450 "value" => [%{"tuple" => [":enabled", true]}]
453 "need_reboot" => true
456 assert post(conn, "/api/pleroma/admin/config", %{
458 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
461 |> json_response(200) == %{
464 "group" => ":pleroma",
467 %{"tuple" => [":key3", 3]}
472 "need_reboot" => true
476 assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
477 end) =~ "pleroma restarted"
481 |> get("/api/pleroma/admin/config")
482 |> json_response(200)
484 assert configs["need_reboot"] == false
487 test "saving config with nested merge", %{conn: conn} do
489 insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
492 post(conn, "/api/pleroma/admin/config", %{
498 %{"tuple" => [":key3", 3]},
503 %{"tuple" => [":k2", 1]},
504 %{"tuple" => [":k3", 3]}
513 assert json_response(conn, 200) == %{
516 "group" => ":pleroma",
519 %{"tuple" => [":key1", 1]},
520 %{"tuple" => [":key3", 3]},
525 %{"tuple" => [":k1", 1]},
526 %{"tuple" => [":k2", 1]},
527 %{"tuple" => [":k3", 3]}
532 "db" => [":key1", ":key3", ":key2"]
538 test "saving special atoms", %{conn: conn} do
540 post(conn, "/api/pleroma/admin/config", %{
543 "group" => ":pleroma",
549 [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
557 assert json_response(conn, 200) == %{
560 "group" => ":pleroma",
566 [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
570 "db" => [":ssl_options"]
575 assert Application.get_env(:pleroma, :key1) == [
576 ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
580 test "saving full setting if value is in full_key_update list", %{conn: conn} do
581 backends = Application.get_env(:logger, :backends)
582 on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
588 value: :erlang.term_to_binary([])
591 Pleroma.Config.TransferTask.load_and_update_env([], false)
593 assert Application.get_env(:logger, :backends) == []
596 post(conn, "/api/pleroma/admin/config", %{
606 assert json_response(conn, 200) == %{
609 "group" => ":logger",
610 "key" => ":backends",
614 "db" => [":backends"]
619 assert Application.get_env(:logger, :backends) == [
624 test "saving full setting if value is not keyword", %{conn: conn} do
629 value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
633 post(conn, "/api/pleroma/admin/config", %{
635 %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
639 assert json_response(conn, 200) == %{
644 "value" => "Tesla.Adapter.Httpc",
651 test "update config setting & delete with fallback to default value", %{
656 ueberauth = Application.get_env(:ueberauth, Ueberauth)
657 config1 = insert(:config, key: ":keyaa1")
658 config2 = insert(:config, key: ":keyaa2")
667 post(conn, "/api/pleroma/admin/config", %{
669 %{group: config1.group, key: config1.key, value: "another_value"},
670 %{group: config2.group, key: config2.key, value: "another_value"}
674 assert json_response(conn, 200) == %{
677 "group" => ":pleroma",
678 "key" => config1.key,
679 "value" => "another_value",
683 "group" => ":pleroma",
684 "key" => config2.key,
685 "value" => "another_value",
691 assert Application.get_env(:pleroma, :keyaa1) == "another_value"
692 assert Application.get_env(:pleroma, :keyaa2) == "another_value"
693 assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
697 |> assign(:user, admin)
698 |> assign(:token, token)
699 |> post("/api/pleroma/admin/config", %{
701 %{group: config2.group, key: config2.key, delete: true},
710 assert json_response(conn, 200) == %{
714 assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
715 refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
718 test "common config example", %{conn: conn} do
720 post(conn, "/api/pleroma/admin/config", %{
723 "group" => ":pleroma",
724 "key" => "Pleroma.Captcha.NotReal",
726 %{"tuple" => [":enabled", false]},
727 %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
728 %{"tuple" => [":seconds_valid", 60]},
729 %{"tuple" => [":path", ""]},
730 %{"tuple" => [":key1", nil]},
731 %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
732 %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
733 %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
734 %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
735 %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
736 %{"tuple" => [":name", "Pleroma"]}
742 assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
744 assert json_response(conn, 200) == %{
747 "group" => ":pleroma",
748 "key" => "Pleroma.Captcha.NotReal",
750 %{"tuple" => [":enabled", false]},
751 %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
752 %{"tuple" => [":seconds_valid", 60]},
753 %{"tuple" => [":path", ""]},
754 %{"tuple" => [":key1", nil]},
755 %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
756 %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
757 %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
758 %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
759 %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
760 %{"tuple" => [":name", "Pleroma"]}
780 test "tuples with more than two values", %{conn: conn} do
782 post(conn, "/api/pleroma/admin/config", %{
785 "group" => ":pleroma",
786 "key" => "Pleroma.Web.Endpoint.NotReal",
803 "Pleroma.Web.MastodonAPI.WebsocketHandler",
810 "Phoenix.Endpoint.CowboyWebSocket",
813 "Phoenix.Transports.WebSocket",
816 "Pleroma.Web.Endpoint",
817 "Pleroma.Web.UserSocket",
828 "Phoenix.Endpoint.Cowboy2Handler",
829 %{"tuple" => ["Pleroma.Web.Endpoint", []]}
846 assert json_response(conn, 200) == %{
849 "group" => ":pleroma",
850 "key" => "Pleroma.Web.Endpoint.NotReal",
867 "Pleroma.Web.MastodonAPI.WebsocketHandler",
874 "Phoenix.Endpoint.CowboyWebSocket",
877 "Phoenix.Transports.WebSocket",
880 "Pleroma.Web.Endpoint",
881 "Pleroma.Web.UserSocket",
892 "Phoenix.Endpoint.Cowboy2Handler",
893 %{"tuple" => ["Pleroma.Web.Endpoint", []]}
912 test "settings with nesting map", %{conn: conn} do
914 post(conn, "/api/pleroma/admin/config", %{
917 "group" => ":pleroma",
920 %{"tuple" => [":key2", "some_val"]},
925 ":max_options" => 20,
926 ":max_option_chars" => 200,
927 ":min_expiration" => 0,
928 ":max_expiration" => 31_536_000,
930 ":max_options" => 20,
931 ":max_option_chars" => 200,
932 ":min_expiration" => 0,
933 ":max_expiration" => 31_536_000
943 assert json_response(conn, 200) ==
947 "group" => ":pleroma",
950 %{"tuple" => [":key2", "some_val"]},
955 ":max_expiration" => 31_536_000,
956 ":max_option_chars" => 200,
957 ":max_options" => 20,
958 ":min_expiration" => 0,
960 ":max_expiration" => 31_536_000,
961 ":max_option_chars" => 200,
962 ":max_options" => 20,
963 ":min_expiration" => 0
969 "db" => [":key2", ":key3"]
975 test "value as map", %{conn: conn} do
977 post(conn, "/api/pleroma/admin/config", %{
980 "group" => ":pleroma",
982 "value" => %{"key" => "some_val"}
987 assert json_response(conn, 200) ==
991 "group" => ":pleroma",
993 "value" => %{"key" => "some_val"},
1000 test "queues key as atom", %{conn: conn} do
1002 post(conn, "/api/pleroma/admin/config", %{
1008 %{"tuple" => [":federator_incoming", 50]},
1009 %{"tuple" => [":federator_outgoing", 50]},
1010 %{"tuple" => [":web_push", 50]},
1011 %{"tuple" => [":mailer", 10]},
1012 %{"tuple" => [":transmogrifier", 20]},
1013 %{"tuple" => [":scheduled_activities", 10]},
1014 %{"tuple" => [":background", 5]}
1020 assert json_response(conn, 200) == %{
1026 %{"tuple" => [":federator_incoming", 50]},
1027 %{"tuple" => [":federator_outgoing", 50]},
1028 %{"tuple" => [":web_push", 50]},
1029 %{"tuple" => [":mailer", 10]},
1030 %{"tuple" => [":transmogrifier", 20]},
1031 %{"tuple" => [":scheduled_activities", 10]},
1032 %{"tuple" => [":background", 5]}
1035 ":federator_incoming",
1036 ":federator_outgoing",
1040 ":scheduled_activities",
1048 test "delete part of settings by atom subkeys", %{conn: conn} do
1052 value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
1056 post(conn, "/api/pleroma/admin/config", %{
1059 group: config.group,
1061 subkeys: [":subkey1", ":subkey3"],
1067 assert json_response(conn, 200) == %{
1070 "group" => ":pleroma",
1072 "value" => [%{"tuple" => [":subkey2", "val2"]}],
1073 "db" => [":subkey2"]
1079 test "proxy tuple localhost", %{conn: conn} do
1081 post(conn, "/api/pleroma/admin/config", %{
1087 %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
1096 "group" => ":pleroma",
1102 } = json_response(conn, 200)
1104 assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
1105 assert ":proxy_url" in db
1108 test "proxy tuple domain", %{conn: conn} do
1110 post(conn, "/api/pleroma/admin/config", %{
1116 %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
1125 "group" => ":pleroma",
1131 } = json_response(conn, 200)
1133 assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
1134 assert ":proxy_url" in db
1137 test "proxy tuple ip", %{conn: conn} do
1139 post(conn, "/api/pleroma/admin/config", %{
1145 %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
1154 "group" => ":pleroma",
1160 } = json_response(conn, 200)
1162 assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
1163 assert ":proxy_url" in db
1166 @tag capture_log: true
1167 test "doesn't set keys not in the whitelist", %{conn: conn} do
1168 clear_config(:database_config_whitelist, [
1171 {:pleroma, Pleroma.Captcha.NotReal},
1175 post(conn, "/api/pleroma/admin/config", %{
1177 %{group: ":pleroma", key: ":key1", value: "value1"},
1178 %{group: ":pleroma", key: ":key2", value: "value2"},
1179 %{group: ":pleroma", key: ":key3", value: "value3"},
1180 %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
1181 %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
1182 %{group: ":not_real", key: ":anything", value: "value6"}
1186 assert Application.get_env(:pleroma, :key1) == "value1"
1187 assert Application.get_env(:pleroma, :key2) == "value2"
1188 assert Application.get_env(:pleroma, :key3) == nil
1189 assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
1190 assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
1191 assert Application.get_env(:not_real, :anything) == "value6"
1195 describe "GET /api/pleroma/admin/config/descriptions" do
1196 test "structure", %{conn: conn} do
1197 admin = insert(:user, is_admin: true)
1200 assign(conn, :user, admin)
1201 |> get("/api/pleroma/admin/config/descriptions")
1203 assert [child | _others] = json_response(conn, 200)
1205 assert child["children"]
1207 assert String.starts_with?(child["group"], ":")
1208 assert child["description"]
1211 test "filters by database configuration whitelist", %{conn: conn} do
1212 clear_config(:database_config_whitelist, [
1213 {:pleroma, :instance},
1214 {:pleroma, :activitypub},
1215 {:pleroma, Pleroma.Upload},
1219 admin = insert(:user, is_admin: true)
1222 assign(conn, :user, admin)
1223 |> get("/api/pleroma/admin/config/descriptions")
1225 children = json_response(conn, 200)
1227 assert length(children) == 4
1229 assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
1231 instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
1232 assert instance["children"]
1234 activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
1235 assert activitypub["children"]
1237 web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
1238 assert web_endpoint["children"]
1240 esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
1241 assert esshd["children"]