Merge branch 'feature/custom-fields' into 'develop'
Add custom profile fields See merge request pleroma/pleroma!1488
This commit is contained in:
commit
ef43016b2c
18 changed files with 365 additions and 27 deletions
|
@ -68,6 +68,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Mastodon API: Add `pleroma.deactivated` to the Account entity
|
- Mastodon API: Add `pleroma.deactivated` to the Account entity
|
||||||
- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
|
- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
|
||||||
- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
|
- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
|
||||||
|
- Mastodon API: Improve support for the user profile custom fields
|
||||||
- Admin API: Return users' tags when querying reports
|
- Admin API: Return users' tags when querying reports
|
||||||
- Admin API: Return avatar and display name when querying users
|
- Admin API: Return avatar and display name when querying users
|
||||||
- Admin API: Allow querying user by ID
|
- Admin API: Allow querying user by ID
|
||||||
|
|
|
@ -255,6 +255,10 @@
|
||||||
dynamic_configuration: false,
|
dynamic_configuration: false,
|
||||||
user_bio_length: 5000,
|
user_bio_length: 5000,
|
||||||
user_name_length: 100,
|
user_name_length: 100,
|
||||||
|
max_account_fields: 10,
|
||||||
|
max_remote_account_fields: 20,
|
||||||
|
account_field_name_length: 255,
|
||||||
|
account_field_value_length: 255,
|
||||||
external_user_synchronization: true
|
external_user_synchronization: true
|
||||||
|
|
||||||
config :pleroma, :markup,
|
config :pleroma, :markup,
|
||||||
|
|
|
@ -132,6 +132,10 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
* `skip_thread_containment`: Skip filter out broken threads. The default is `false`.
|
* `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`.
|
* `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.
|
* `dynamic_configuration`: Allow transferring configuration to DB with the subsequent customization from Admin api.
|
||||||
|
* `max_account_fields`: The maximum number of custom fields in the user profile (default: `10`)
|
||||||
|
* `max_remote_account_fields`: The maximum number of custom fields in the remote user profile (default: `20`)
|
||||||
|
* `account_field_name_length`: An account field name maximum length (default: `255`)
|
||||||
|
* `account_field_value_length`: An account field value maximum length (default: `255`)
|
||||||
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -282,3 +282,31 @@ def scrub({tag, attributes, children}), do: {tag, attributes, children}
|
||||||
def scrub({_tag, children}), do: children
|
def scrub({_tag, children}), do: children
|
||||||
def scrub(text), do: text
|
def scrub(text), do: text
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule Pleroma.HTML.Scrubber.LinksOnly do
|
||||||
|
@moduledoc """
|
||||||
|
An HTML scrubbing policy which limits to links only.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])
|
||||||
|
|
||||||
|
require HtmlSanitizeEx.Scrubber.Meta
|
||||||
|
alias HtmlSanitizeEx.Scrubber.Meta
|
||||||
|
|
||||||
|
Meta.remove_cdata_sections_before_scrub()
|
||||||
|
Meta.strip_comments()
|
||||||
|
|
||||||
|
# links
|
||||||
|
Meta.allow_tag_with_uri_attributes("a", ["href"], @valid_schemes)
|
||||||
|
|
||||||
|
Meta.allow_tag_with_this_attribute_values("a", "rel", [
|
||||||
|
"tag",
|
||||||
|
"nofollow",
|
||||||
|
"noopener",
|
||||||
|
"noreferrer",
|
||||||
|
"me"
|
||||||
|
])
|
||||||
|
|
||||||
|
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
|
||||||
|
Meta.strip_everything_not_covered()
|
||||||
|
end
|
||||||
|
|
|
@ -222,12 +222,12 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
|> validate_length(:name, min: 1, max: name_limit)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
def upgrade_changeset(struct, params \\ %{}) do
|
def upgrade_changeset(struct, params \\ %{}, remote? \\ false) do
|
||||||
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
|
||||||
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
|
||||||
|
|
||||||
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
params = Map.put(params, :last_refreshed_at, NaiveDateTime.utc_now())
|
||||||
info_cng = User.Info.user_upgrade(struct.info, params[:info])
|
info_cng = User.Info.user_upgrade(struct.info, params[:info], remote?)
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
|
|
|
@ -49,6 +49,8 @@ defmodule Pleroma.User.Info do
|
||||||
field(:mascot, :map, default: nil)
|
field(:mascot, :map, default: nil)
|
||||||
field(:emoji, {:array, :map}, default: [])
|
field(:emoji, {:array, :map}, default: [])
|
||||||
field(:pleroma_settings_store, :map, default: %{})
|
field(:pleroma_settings_store, :map, default: %{})
|
||||||
|
field(:fields, {:array, :map}, default: [])
|
||||||
|
field(:raw_fields, {:array, :map}, default: [])
|
||||||
|
|
||||||
field(:notification_settings, :map,
|
field(:notification_settings, :map,
|
||||||
default: %{
|
default: %{
|
||||||
|
@ -254,11 +256,13 @@ def remote_user_creation(info, params) do
|
||||||
:hide_followers,
|
:hide_followers,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
:follower_count,
|
:follower_count,
|
||||||
|
:fields,
|
||||||
:following_count
|
:following_count
|
||||||
])
|
])
|
||||||
|
|> validate_fields(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_upgrade(info, params) do
|
def user_upgrade(info, params, remote? \\ false) do
|
||||||
info
|
info
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
:ap_enabled,
|
:ap_enabled,
|
||||||
|
@ -269,8 +273,10 @@ def user_upgrade(info, params) do
|
||||||
:follower_count,
|
:follower_count,
|
||||||
:following_count,
|
:following_count,
|
||||||
:hide_follows,
|
:hide_follows,
|
||||||
|
:fields,
|
||||||
:hide_followers
|
:hide_followers
|
||||||
])
|
])
|
||||||
|
|> validate_fields(remote?)
|
||||||
end
|
end
|
||||||
|
|
||||||
def profile_update(info, params) do
|
def profile_update(info, params) do
|
||||||
|
@ -286,10 +292,40 @@ def profile_update(info, params) do
|
||||||
:background,
|
:background,
|
||||||
:show_role,
|
:show_role,
|
||||||
:skip_thread_containment,
|
:skip_thread_containment,
|
||||||
|
:fields,
|
||||||
|
:raw_fields,
|
||||||
:pleroma_settings_store
|
:pleroma_settings_store
|
||||||
])
|
])
|
||||||
|
|> validate_fields()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_fields(changeset, remote? \\ false) do
|
||||||
|
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||||
|
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
||||||
|
|
||||||
|
changeset
|
||||||
|
|> validate_length(:fields, max: limit)
|
||||||
|
|> validate_change(:fields, fn :fields, fields ->
|
||||||
|
if Enum.all?(fields, &valid_field?/1) do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[fields: "invalid"]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_field?(%{"name" => name, "value" => value}) do
|
||||||
|
name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255)
|
||||||
|
value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255)
|
||||||
|
|
||||||
|
is_binary(name) &&
|
||||||
|
is_binary(value) &&
|
||||||
|
String.length(name) <= name_limit &&
|
||||||
|
String.length(value) <= value_limit
|
||||||
|
end
|
||||||
|
|
||||||
|
defp valid_field?(_), do: false
|
||||||
|
|
||||||
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
|
@spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()
|
||||||
def confirmation_changeset(info, opts) do
|
def confirmation_changeset(info, opts) do
|
||||||
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
need_confirmation? = Keyword.get(opts, :need_confirmation)
|
||||||
|
@ -384,6 +420,19 @@ def remove_reblog_mute(info, ap_id) do
|
||||||
cast(info, params, [:muted_reblogs])
|
cast(info, params, [:muted_reblogs])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
||||||
|
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
||||||
|
def fields(%{fields: [], source_data: %{"attachment" => attachment}}) do
|
||||||
|
limit = Pleroma.Config.get([:instance, :max_remote_account_fields], 0)
|
||||||
|
|
||||||
|
attachment
|
||||||
|
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||||
|
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||||
|
|> Enum.take(limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fields(%{fields: fields}), do: fields
|
||||||
|
|
||||||
def follow_information_update(info, params) do
|
def follow_information_update(info, params) do
|
||||||
info
|
info
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
|
|
|
@ -1023,6 +1023,12 @@ defp object_to_user_data(data) do
|
||||||
"url" => [%{"href" => data["image"]["url"]}]
|
"url" => [%{"href" => data["image"]["url"]}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fields =
|
||||||
|
data
|
||||||
|
|> Map.get("attachment", [])
|
||||||
|
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||||
|
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||||
|
|
||||||
locked = data["manuallyApprovesFollowers"] || false
|
locked = data["manuallyApprovesFollowers"] || false
|
||||||
data = Transmogrifier.maybe_fix_user_object(data)
|
data = Transmogrifier.maybe_fix_user_object(data)
|
||||||
|
|
||||||
|
@ -1032,6 +1038,7 @@ defp object_to_user_data(data) do
|
||||||
ap_enabled: true,
|
ap_enabled: true,
|
||||||
source_data: data,
|
source_data: data,
|
||||||
banner: banner,
|
banner: banner,
|
||||||
|
fields: fields,
|
||||||
locked: locked
|
locked: locked
|
||||||
},
|
},
|
||||||
avatar: avatar,
|
avatar: avatar,
|
||||||
|
|
|
@ -598,14 +598,20 @@ def handle_incoming(
|
||||||
|
|
||||||
banner = new_user_data[:info][:banner]
|
banner = new_user_data[:info][:banner]
|
||||||
locked = new_user_data[:info][:locked] || false
|
locked = new_user_data[:info][:locked] || false
|
||||||
|
attachment = get_in(new_user_data, [:info, :source_data, "attachment"]) || []
|
||||||
|
|
||||||
|
fields =
|
||||||
|
attachment
|
||||||
|
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
||||||
|
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
||||||
|
|
||||||
update_data =
|
update_data =
|
||||||
new_user_data
|
new_user_data
|
||||||
|> Map.take([:name, :bio, :avatar])
|
|> Map.take([:name, :bio, :avatar])
|
||||||
|> Map.put(:info, %{banner: banner, locked: locked})
|
|> Map.put(:info, %{banner: banner, locked: locked, fields: fields})
|
||||||
|
|
||||||
actor
|
actor
|
||||||
|> User.upgrade_changeset(update_data)
|
|> User.upgrade_changeset(update_data, true)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
ActivityPub.update(%{
|
ActivityPub.update(%{
|
||||||
|
|
|
@ -80,6 +80,17 @@ def render("user.json", %{user: user}) do
|
||||||
|> Transmogrifier.add_emoji_tags()
|
|> Transmogrifier.add_emoji_tags()
|
||||||
|> Map.get("tag", [])
|
|> Map.get("tag", [])
|
||||||
|
|
||||||
|
fields =
|
||||||
|
user.info
|
||||||
|
|> User.Info.fields()
|
||||||
|
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||||
|
%{
|
||||||
|
"name" => Pleroma.HTML.strip_tags(name),
|
||||||
|
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|> Enum.map(&Map.put(&1, "type", "PropertyValue"))
|
||||||
|
|
||||||
%{
|
%{
|
||||||
"id" => user.ap_id,
|
"id" => user.ap_id,
|
||||||
"type" => "Person",
|
"type" => "Person",
|
||||||
|
@ -98,6 +109,7 @@ def render("user.json", %{user: user}) do
|
||||||
"publicKeyPem" => public_key
|
"publicKeyPem" => public_key
|
||||||
},
|
},
|
||||||
"endpoints" => endpoints,
|
"endpoints" => endpoints,
|
||||||
|
"attachment" => fields,
|
||||||
"tag" => (user.info.source_data["tag"] || []) ++ user_tags
|
"tag" => (user.info.source_data["tag"] || []) ++ user_tags
|
||||||
}
|
}
|
||||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
||||||
|
|
|
@ -138,7 +138,9 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
|
emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
|
||||||
|
|
||||||
user_info_emojis =
|
user_info_emojis =
|
||||||
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|
user.info
|
||||||
|
|> Map.get(:emoji, [])
|
||||||
|
|> Enum.concat(Formatter.get_emoji_map(emojis_text))
|
||||||
|> Enum.dedup()
|
|> Enum.dedup()
|
||||||
|
|
||||||
info_params =
|
info_params =
|
||||||
|
@ -157,6 +159,12 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|> add_if_present(params, "default_scope", :default_scope)
|
|> add_if_present(params, "default_scope", :default_scope)
|
||||||
|
|> add_if_present(params, "fields", :fields, fn fields ->
|
||||||
|
fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
|
||||||
|
|
||||||
|
{:ok, fields}
|
||||||
|
end)
|
||||||
|
|> add_if_present(params, "fields", :raw_fields)
|
||||||
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
|
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
|
||||||
{:ok, Map.merge(user.info.pleroma_settings_store, value)}
|
{:ok, Map.merge(user.info.pleroma_settings_store, value)}
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -94,12 +94,18 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
fields =
|
fields =
|
||||||
(user.info.source_data["attachment"] || [])
|
user.info
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
|> User.Info.fields()
|
||||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||||
|
%{
|
||||||
|
"name" => Pleroma.HTML.strip_tags(name),
|
||||||
|
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
raw_fields = Map.get(user.info, :raw_fields, [])
|
||||||
|
|
||||||
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
|
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
|
||||||
|
|
||||||
relationship = render("relationship.json", %{user: opts[:for], target: user})
|
relationship = render("relationship.json", %{user: opts[:for], target: user})
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
@ -124,6 +130,7 @@ defp do_render("account.json", %{user: user} = opts) do
|
||||||
source: %{
|
source: %{
|
||||||
note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
|
fields: raw_fields,
|
||||||
pleroma: %{}
|
pleroma: %{}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -74,12 +74,15 @@ defp do_render("user.json", %{user: user = %User{}} = assigns) do
|
||||||
|> HTML.filter_tags(User.html_filter_policy(for_user))
|
|> HTML.filter_tags(User.html_filter_policy(for_user))
|
||||||
|> Formatter.emojify(emoji)
|
|> Formatter.emojify(emoji)
|
||||||
|
|
||||||
# ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``.
|
|
||||||
# For example: [{"name": "Pronoun", "value": "she/her"}, …]
|
|
||||||
fields =
|
fields =
|
||||||
(user.info.source_data["attachment"] || [])
|
user.info
|
||||||
|> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
|
|> User.Info.fields()
|
||||||
|> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
|
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||||
|
%{
|
||||||
|
"name" => Pleroma.HTML.strip_tags(name),
|
||||||
|
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
data =
|
data =
|
||||||
%{
|
%{
|
||||||
|
|
36
test/fixtures/mastodon-update.json
vendored
36
test/fixtures/mastodon-update.json
vendored
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"type": "Update",
|
"type": "Update",
|
||||||
"object": {
|
"object": {
|
||||||
"url": "http://mastodon.example.org/@gargron",
|
"url": "http://mastodon.example.org/@gargron",
|
||||||
"type": "Person",
|
"type": "Person",
|
||||||
"summary": "<p>Some bio</p>",
|
"summary": "<p>Some bio</p>",
|
||||||
"publicKey": {
|
"publicKey": {
|
||||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gs3VnQf6am3R+CeBV4H\nlfI1HZTNRIBHgvFszRZkCERbRgEWMu+P+I6/7GJC5H5jhVQ60z4MmXcyHOGmYMK/\n5XyuHQz7V2Ssu1AxLfRN5Biq1ayb0+DT/E7QxNXDJPqSTnstZ6C7zKH/uAETqg3l\nBonjCQWyds+IYbQYxf5Sp3yhvQ80lMwHML3DaNCMlXWLoOnrOX5/yK5+dedesg2\n/HIvGk+HEt36vm6hoH7bwPuEkgA++ACqwjXRe5Mta7i3eilHxFaF8XIrJFARV0t\nqOu4GID/jG6oA+swIWndGrtR2QRJIt9QIBFfK3HG5M0koZbY1eTqwNFRHFL3xaD\nUQIDAQAB\n-----END PUBLIC KEY-----\n",
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0gs3VnQf6am3R+CeBV4H\nlfI1HZTNRIBHgvFszRZkCERbRgEWMu+P+I6/7GJC5H5jhVQ60z4MmXcyHOGmYMK/\n5XyuHQz7V2Ssu1AxLfRN5Biq1ayb0+DT/E7QxNXDJPqSTnstZ6C7zKH/uAETqg3l\nBonjCQWyds+IYbQYxf5Sp3yhvQ80lMwHML3DaNCMlXWLoOnrOX5/yK5+dedesg2\n/HIvGk+HEt36vm6hoH7bwPuEkgA++ACqwjXRe5Mta7i3eilHxFaF8XIrJFARV0t\nqOu4GID/jG6oA+swIWndGrtR2QRJIt9QIBFfK3HG5M0koZbY1eTqwNFRHFL3xaD\nUQIDAQAB\n-----END PUBLIC KEY-----\n",
|
||||||
"owner": "http://mastodon.example.org/users/gargron",
|
"owner": "http://mastodon.example.org/users/gargron",
|
||||||
"id": "http://mastodon.example.org/users/gargron#main-key"
|
"id": "http://mastodon.example.org/users/gargron#main-key"
|
||||||
|
@ -20,7 +20,27 @@
|
||||||
"endpoints": {
|
"endpoints": {
|
||||||
"sharedInbox": "http://mastodon.example.org/inbox"
|
"sharedInbox": "http://mastodon.example.org/inbox"
|
||||||
},
|
},
|
||||||
"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"},"image":{"type":"Image","mediaType":"image/png","url":"https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}
|
"attachment": [{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "foo",
|
||||||
|
"value": "updated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "foo1",
|
||||||
|
"value": "updated"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/jpeg",
|
||||||
|
"url": "https://cd.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"url": "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"id": "http://mastodon.example.org/users/gargron#updates/1519563538",
|
"id": "http://mastodon.example.org/users/gargron#updates/1519563538",
|
||||||
"actor": "http://mastodon.example.org/users/gargron",
|
"actor": "http://mastodon.example.org/users/gargron",
|
||||||
|
|
|
@ -1 +1,54 @@
|
||||||
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","movedTo":"as:movedTo","Hashtag":"as:Hashtag","ostatus":"http://ostatus.org#","atomUri":"ostatus:atomUri","inReplyToAtomUri":"ostatus:inReplyToAtomUri","conversation":"ostatus:conversation","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji"}],"id":"http://mastodon.example.org/users/admin","type":"Person","following":"http://mastodon.example.org/users/admin/following","followers":"http://mastodon.example.org/users/admin/followers","inbox":"http://mastodon.example.org/users/admin/inbox","outbox":"http://mastodon.example.org/users/admin/outbox","preferredUsername":"admin","name":null,"summary":"\u003cp\u003e\u003c/p\u003e","url":"http://mastodon.example.org/@admin","manuallyApprovesFollowers":false,"publicKey":{"id":"http://mastodon.example.org/users/admin#main-key","owner":"http://mastodon.example.org/users/admin","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n"},"endpoints":{"sharedInbox":"http://mastodon.example.org/inbox"},"icon":{"type":"Image","mediaType":"image/jpeg","url":"https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"},"image":{"type":"Image","mediaType":"image/png","url":"https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"}}
|
{
|
||||||
|
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", {
|
||||||
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"movedTo": "as:movedTo",
|
||||||
|
"Hashtag": "as:Hashtag",
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"Emoji": "toot:Emoji"
|
||||||
|
}],
|
||||||
|
"id": "http://mastodon.example.org/users/admin",
|
||||||
|
"type": "Person",
|
||||||
|
"following": "http://mastodon.example.org/users/admin/following",
|
||||||
|
"followers": "http://mastodon.example.org/users/admin/followers",
|
||||||
|
"inbox": "http://mastodon.example.org/users/admin/inbox",
|
||||||
|
"outbox": "http://mastodon.example.org/users/admin/outbox",
|
||||||
|
"preferredUsername": "admin",
|
||||||
|
"name": null,
|
||||||
|
"summary": "\u003cp\u003e\u003c/p\u003e",
|
||||||
|
"url": "http://mastodon.example.org/@admin",
|
||||||
|
"manuallyApprovesFollowers": false,
|
||||||
|
"publicKey": {
|
||||||
|
"id": "http://mastodon.example.org/users/admin#main-key",
|
||||||
|
"owner": "http://mastodon.example.org/users/admin",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
},
|
||||||
|
"attachment": [{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "foo",
|
||||||
|
"value": "bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "PropertyValue",
|
||||||
|
"name": "foo1",
|
||||||
|
"value": "bar1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"endpoints": {
|
||||||
|
"sharedInbox": "http://mastodon.example.org/inbox"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/jpeg",
|
||||||
|
"url": "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"mediaType": "image/png",
|
||||||
|
"url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -509,6 +509,60 @@ test "it works for incoming update activities" do
|
||||||
assert user.bio == "<p>Some bio</p>"
|
assert user.bio == "<p>Some bio</p>"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works with custom profile fields" do
|
||||||
|
{:ok, activity} =
|
||||||
|
"test/fixtures/mastodon-post-activity.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Transmogrifier.handle_incoming()
|
||||||
|
|
||||||
|
user = User.get_cached_by_ap_id(activity.actor)
|
||||||
|
|
||||||
|
assert User.Info.fields(user.info) == [
|
||||||
|
%{"name" => "foo", "value" => "bar"},
|
||||||
|
%{"name" => "foo1", "value" => "bar1"}
|
||||||
|
]
|
||||||
|
|
||||||
|
update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
object =
|
||||||
|
update_data["object"]
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|> Map.put("id", user.ap_id)
|
||||||
|
|
||||||
|
update_data =
|
||||||
|
update_data
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|
||||||
|
{:ok, _update_activity} = Transmogrifier.handle_incoming(update_data)
|
||||||
|
|
||||||
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
|
|
||||||
|
assert User.Info.fields(user.info) == [
|
||||||
|
%{"name" => "foo", "value" => "updated"},
|
||||||
|
%{"name" => "foo1", "value" => "updated"}
|
||||||
|
]
|
||||||
|
|
||||||
|
Pleroma.Config.put([:instance, :max_remote_account_fields], 2)
|
||||||
|
|
||||||
|
update_data =
|
||||||
|
put_in(update_data, ["object", "attachment"], [
|
||||||
|
%{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
|
||||||
|
%{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"},
|
||||||
|
%{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"}
|
||||||
|
])
|
||||||
|
|
||||||
|
{:ok, _} = Transmogrifier.handle_incoming(update_data)
|
||||||
|
|
||||||
|
user = User.get_cached_by_ap_id(user.ap_id)
|
||||||
|
|
||||||
|
assert User.Info.fields(user.info) == [
|
||||||
|
%{"name" => "foo", "value" => "updated"},
|
||||||
|
%{"name" => "foo1", "value" => "updated"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
test "it works for incoming update activities which lock the account" do
|
test "it works for incoming update activities which lock the account" do
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,21 @@ test "Renders a user, including the public key" do
|
||||||
assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")
|
assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Renders profile fields" do
|
||||||
|
fields = [
|
||||||
|
%{"name" => "foo", "value" => "bar"}
|
||||||
|
]
|
||||||
|
|
||||||
|
{:ok, user} =
|
||||||
|
insert(:user)
|
||||||
|
|> User.upgrade_changeset(%{info: %{fields: fields}})
|
||||||
|
|> User.update_and_set_cache()
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
"attachment" => [%{"name" => "foo", "type" => "PropertyValue", "value" => "bar"}]
|
||||||
|
} = UserView.render("user.json", %{user: user})
|
||||||
|
end
|
||||||
|
|
||||||
test "Does not add an avatar image if the user hasn't set one" do
|
test "Does not add an avatar image if the user hasn't set one" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, user} = User.ensure_keys_present(user)
|
{:ok, user} = User.ensure_keys_present(user)
|
||||||
|
|
|
@ -67,7 +67,8 @@ test "Represent a user account" do
|
||||||
source: %{
|
source: %{
|
||||||
note: "valid html",
|
note: "valid html",
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
pleroma: %{}
|
pleroma: %{},
|
||||||
|
fields: []
|
||||||
},
|
},
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
background_image: "https://example.com/images/asuka_hospital.png",
|
background_image: "https://example.com/images/asuka_hospital.png",
|
||||||
|
@ -134,7 +135,8 @@ test "Represent a Service(bot) account" do
|
||||||
source: %{
|
source: %{
|
||||||
note: user.bio,
|
note: user.bio,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
pleroma: %{}
|
pleroma: %{},
|
||||||
|
fields: []
|
||||||
},
|
},
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
background_image: nil,
|
background_image: nil,
|
||||||
|
@ -304,7 +306,8 @@ test "represent an embedded relationship" do
|
||||||
source: %{
|
source: %{
|
||||||
note: user.bio,
|
note: user.bio,
|
||||||
sensitive: false,
|
sensitive: false,
|
||||||
pleroma: %{}
|
pleroma: %{},
|
||||||
|
fields: []
|
||||||
},
|
},
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
background_image: nil,
|
background_image: nil,
|
||||||
|
|
|
@ -300,5 +300,69 @@ test "updates profile emojos", %{conn: conn} do
|
||||||
assert user["display_name"] == name
|
assert user["display_name"] == name
|
||||||
assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
|
assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "update fields", %{conn: conn} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
|
||||||
|
%{"name" => "link", "value" => "cofe.io"}
|
||||||
|
]
|
||||||
|
|
||||||
|
account =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"fields" => fields})
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert account["fields"] == [
|
||||||
|
%{"name" => "foo", "value" => "bar"},
|
||||||
|
%{"name" => "link", "value" => "<a href=\"http://cofe.io\">cofe.io</a>"}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert account["source"]["fields"] == [
|
||||||
|
%{
|
||||||
|
"name" => "<a href=\"http://google.com\">foo</a>",
|
||||||
|
"value" => "<script>bar</script>"
|
||||||
|
},
|
||||||
|
%{"name" => "link", "value" => "cofe.io"}
|
||||||
|
]
|
||||||
|
|
||||||
|
name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
|
||||||
|
value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
|
||||||
|
|
||||||
|
long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
|
||||||
|
|
||||||
|
fields = [%{"name" => "<b>foo<b>", "value" => long_value}]
|
||||||
|
|
||||||
|
assert %{"error" => "Invalid request"} ==
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"fields" => fields})
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
|
long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
|
||||||
|
|
||||||
|
fields = [%{"name" => long_name, "value" => "bar"}]
|
||||||
|
|
||||||
|
assert %{"error" => "Invalid request"} ==
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"fields" => fields})
|
||||||
|
|> json_response(403)
|
||||||
|
|
||||||
|
Pleroma.Config.put([:instance, :max_account_fields], 1)
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
%{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
|
||||||
|
%{"name" => "link", "value" => "cofe.io"}
|
||||||
|
]
|
||||||
|
|
||||||
|
assert %{"error" => "Invalid request"} ==
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> patch("/api/v1/accounts/update_credentials", %{"fields" => fields})
|
||||||
|
|> json_response(403)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue