|> validate_format(:nickname, @email_regex)
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, max: name_limit)
- |> validate_fields(true)
+ |> validate_fields(true, struct)
|> validate_non_local()
end
:pleroma_settings_store,
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
)
- |> validate_fields(false)
+ |> validate_fields(false, struct)
end
defp put_fields(changeset) do
|> update_and_set_cache()
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 = Config.get([:instance, limit_name], 0)
[fields: "invalid"]
end
end)
+ |> maybe_validate_rel_me_field(struct)
end
defp valid_field?(%{"name" => name, "value" => value}) do
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
{name, _chopped} =
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
# - display name
def sanitize_html(%User{} = user, filter) do
fields =
- Enum.map(user.fields, fn %{"name" => name, "value" => value} ->
- %{
- "name" => name,
- "value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
- }
+ Enum.map(user.fields, fn %{"value" => value} = field ->
+ Map.put(field, "value", HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly))
end)
user
]
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
fields = [
%{name: ":firefox:", value: "is best 2hu"},