Add link verification in profile fields (#405)
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk> Reviewed-on: #405
This commit is contained in:
parent
1121deb078
commit
a5e98083f2
5 changed files with 144 additions and 11 deletions
|
@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Prometheus metrics exporting from `/api/v1/akkoma/metrics`
|
- Prometheus metrics exporting from `/api/v1/akkoma/metrics`
|
||||||
- Ability to alter http pool size
|
- Ability to alter http pool size
|
||||||
- Translation of statuses via ArgosTranslate
|
- Translation of statuses via ArgosTranslate
|
||||||
|
- Ability to "verify" links in profile fields via rel=me
|
||||||
|
- Mix tasks to dump/load config to/from json for bulk editing
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Non-finch HTTP adapters
|
- Non-finch HTTP adapters
|
||||||
|
|
|
@ -159,7 +159,8 @@ defp cachex_children do
|
||||||
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
||||||
build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500),
|
build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500),
|
||||||
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
||||||
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000)
|
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000),
|
||||||
|
build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300)
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -479,7 +479,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
|> validate_format(:nickname, @email_regex)
|
|> validate_format(:nickname, @email_regex)
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: name_limit)
|
|> validate_length(:name, max: name_limit)
|
||||||
|> validate_fields(true)
|
|> validate_fields(true, struct)
|
||||||
|> validate_non_local()
|
|> validate_non_local()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -549,7 +549,7 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||||
)
|
)
|
||||||
|> validate_fields(false)
|
|> validate_fields(false, struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_fields(changeset) do
|
defp put_fields(changeset) do
|
||||||
|
@ -2359,7 +2359,8 @@ def update_background(user, background) do
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_fields(changeset, remote? \\ false) do
|
@spec validate_fields(Ecto.Changeset.t(), Boolean.t(), User.t()) :: Ecto.Changeset.t()
|
||||||
|
def validate_fields(changeset, remote? \\ false, struct) do
|
||||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||||
limit = Config.get([:instance, limit_name], 0)
|
limit = Config.get([:instance, limit_name], 0)
|
||||||
|
|
||||||
|
@ -2372,6 +2373,7 @@ def validate_fields(changeset, remote? \\ false) do
|
||||||
[fields: "invalid"]
|
[fields: "invalid"]
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|> maybe_validate_rel_me_field(struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp valid_field?(%{"name" => name, "value" => value}) do
|
defp valid_field?(%{"name" => name, "value" => value}) do
|
||||||
|
@ -2384,6 +2386,75 @@ defp valid_field?(%{"name" => name, "value" => value}) do
|
||||||
|
|
||||||
defp valid_field?(_), do: false
|
defp valid_field?(_), do: false
|
||||||
|
|
||||||
|
defp is_url(nil), do: nil
|
||||||
|
|
||||||
|
defp is_url(uri) do
|
||||||
|
case URI.parse(uri) do
|
||||||
|
%URI{host: nil} -> false
|
||||||
|
%URI{scheme: nil} -> false
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec maybe_validate_rel_me_field(Changeset.t(), User.t()) :: Changeset.t()
|
||||||
|
defp maybe_validate_rel_me_field(changeset, %User{ap_id: _ap_id} = struct) do
|
||||||
|
fields = get_change(changeset, :fields)
|
||||||
|
raw_fields = get_change(changeset, :raw_fields)
|
||||||
|
|
||||||
|
if is_nil(fields) do
|
||||||
|
changeset
|
||||||
|
else
|
||||||
|
validate_rel_me_field(changeset, fields, raw_fields, struct)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_validate_rel_me_field(changeset, _), do: changeset
|
||||||
|
|
||||||
|
@spec validate_rel_me_field(Changeset.t(), [Map.t()], [Map.t()], User.t()) :: Changeset.t()
|
||||||
|
defp validate_rel_me_field(changeset, fields, raw_fields, %User{
|
||||||
|
nickname: nickname,
|
||||||
|
ap_id: ap_id
|
||||||
|
}) do
|
||||||
|
fields =
|
||||||
|
fields
|
||||||
|
|> Enum.with_index()
|
||||||
|
|> Enum.map(fn {%{"name" => name, "value" => value}, index} ->
|
||||||
|
raw_value =
|
||||||
|
if is_nil(raw_fields) do
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
Enum.at(raw_fields, index)["value"]
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_url(raw_value) do
|
||||||
|
frontend_url =
|
||||||
|
Pleroma.Web.Router.Helpers.redirect_url(
|
||||||
|
Pleroma.Web.Endpoint,
|
||||||
|
:redirector_with_meta,
|
||||||
|
nickname
|
||||||
|
)
|
||||||
|
|
||||||
|
possible_urls = [ap_id, frontend_url]
|
||||||
|
|
||||||
|
with "me" <- RelMe.maybe_put_rel_me(raw_value, possible_urls) do
|
||||||
|
%{
|
||||||
|
"name" => name,
|
||||||
|
"value" => value,
|
||||||
|
"verified_at" => DateTime.to_iso8601(DateTime.utc_now())
|
||||||
|
}
|
||||||
|
else
|
||||||
|
e ->
|
||||||
|
Logger.error("Could not check for rel=me, #{inspect(e)}")
|
||||||
|
%{"name" => name, "value" => value}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
%{"name" => name, "value" => value}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
put_change(changeset, :fields, fields)
|
||||||
|
end
|
||||||
|
|
||||||
defp truncate_field(%{"name" => name, "value" => value}) do
|
defp truncate_field(%{"name" => name, "value" => value}) do
|
||||||
{name, _chopped} =
|
{name, _chopped} =
|
||||||
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
|
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
|
||||||
|
@ -2551,11 +2622,8 @@ def sanitize_html(%User{} = user) do
|
||||||
# - display name
|
# - display name
|
||||||
def sanitize_html(%User{} = user, filter) do
|
def sanitize_html(%User{} = user, filter) do
|
||||||
fields =
|
fields =
|
||||||
Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
|
Enum.map(user.fields, fn %{"value" => value} = field ->
|
||||||
%{
|
Map.put(field, "value", HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly))
|
||||||
"name" => name,
|
|
||||||
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
|
||||||
}
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
user
|
user
|
||||||
|
|
|
@ -38,12 +38,11 @@ defp parse_url(url) do
|
||||||
|
|
||||||
def maybe_put_rel_me("http" <> _ = target_page, profile_urls) when is_list(profile_urls) do
|
def maybe_put_rel_me("http" <> _ = target_page, profile_urls) when is_list(profile_urls) do
|
||||||
{:ok, rel_me_hrefs} = parse(target_page)
|
{:ok, rel_me_hrefs} = parse(target_page)
|
||||||
|
|
||||||
true = Enum.any?(rel_me_hrefs, fn x -> x in profile_urls end)
|
true = Enum.any?(rel_me_hrefs, fn x -> x in profile_urls end)
|
||||||
|
|
||||||
"me"
|
"me"
|
||||||
rescue
|
rescue
|
||||||
_ -> nil
|
e -> nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def maybe_put_rel_me(_, _) do
|
def maybe_put_rel_me(_, _) do
|
||||||
|
|
|
@ -465,6 +465,69 @@ test "update fields", %{conn: conn} do
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "update fields with a link to content with rel=me, with ap id", %{user: user, conn: conn} do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "http://example.com/rel_me/ap_id"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: ~s[<html><head><link rel="me" href="#{user.ap_id}"></head></html>]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
field = %{name: "Website", value: "http://example.com/rel_me/ap_id"}
|
||||||
|
|
||||||
|
account_data =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{fields_attributes: [field]})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"name" => "Website",
|
||||||
|
"value" =>
|
||||||
|
~s[<a href="http://example.com/rel_me/ap_id" rel="ugc">http://example.com/rel_me/ap_id</a>],
|
||||||
|
"verified_at" => verified_at
|
||||||
|
}
|
||||||
|
] = account_data["fields"]
|
||||||
|
|
||||||
|
{:ok, verified_at, _} = DateTime.from_iso8601(verified_at)
|
||||||
|
assert DateTime.diff(DateTime.utc_now(), verified_at) < 10
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update fields with a link to content with rel=me, with frontend path", %{
|
||||||
|
user: user,
|
||||||
|
conn: conn
|
||||||
|
} do
|
||||||
|
fe_url = "#{Pleroma.Web.Endpoint.url()}/#{user.nickname}"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{url: "http://example.com/rel_me/fe_path"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: ~s[<html><head><link rel="me" href="#{fe_url}"></head></html>]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
field = %{name: "Website", value: "http://example.com/rel_me/fe_path"}
|
||||||
|
|
||||||
|
account_data =
|
||||||
|
conn
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{fields_attributes: [field]})
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"name" => "Website",
|
||||||
|
"value" =>
|
||||||
|
~s[<a href="http://example.com/rel_me/fe_path" rel="ugc">http://example.com/rel_me/fe_path</a>],
|
||||||
|
"verified_at" => verified_at
|
||||||
|
}
|
||||||
|
] = account_data["fields"]
|
||||||
|
|
||||||
|
{:ok, verified_at, _} = DateTime.from_iso8601(verified_at)
|
||||||
|
assert DateTime.diff(DateTime.utc_now(), verified_at) < 10
|
||||||
|
end
|
||||||
|
|
||||||
test "emojis in fields labels", %{conn: conn} do
|
test "emojis in fields labels", %{conn: conn} do
|
||||||
fields = [
|
fields = [
|
||||||
%{name: ":firefox:", value: "is best 2hu"},
|
%{name: ":firefox:", value: "is best 2hu"},
|
||||||
|
|
Loading…
Reference in a new issue