Merge branch 'develop' into refactor/notification_settings

This commit is contained in:
Mark Felder 2020-07-13 13:32:21 -05:00
commit 80c21100db
314 changed files with 5798 additions and 2411 deletions

1
.gitignore vendored
View file

@ -5,6 +5,7 @@
/*.ez
/test/uploads
/.elixir_ls
/test/fixtures/DSCN0010_tmp.jpg
/test/fixtures/test_tmp.txt
/test/fixtures/image_tmp.jpg
/test/tmp/

View file

@ -58,26 +58,25 @@ unit-testing:
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- apt-get update && apt-get install -y libimage-exiftool-perl
- mix deps.get
- mix ecto.create
- mix ecto.migrate
- mix coveralls --preload-modules
# Removed to fix CI issue. In this early state it wasn't adding much value anyway.
# TODO Fix and reinstate federated testing
# federated-testing:
# stage: test
# cache: *testing_cache_policy
# services:
# - name: minibikini/postgres-with-rum:12
# alias: postgres
# command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
# script:
# - mix deps.get
# - mix ecto.create
# - mix ecto.migrate
# - epmd -daemon
# - mix test --trace --only federated
federated-testing:
stage: test
cache: *testing_cache_policy
services:
- name: minibikini/postgres-with-rum:12
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- mix deps.get
- mix ecto.create
- mix ecto.migrate
- epmd -daemon
- mix test --trace --only federated
unit-testing-rum:
stage: test
@ -91,6 +90,7 @@ unit-testing-rum:
<<: *global_variables
RUM_ENABLED: "true"
script:
- apt-get update && apt-get install -y libimage-exiftool-perl
- mix deps.get
- mix ecto.create
- mix ecto.migrate

View file

@ -14,7 +14,7 @@
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
* Operating system:
* PostgreSQL version (`postgres -V`):
* PostgreSQL version (`psql -V`):
### Bug description

View file

@ -16,7 +16,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<details>
<summary>API Changes</summary>
- **Breaking:** Pleroma API: The routes to update avatar, banner and background have been removed.
- **Breaking:** Image description length is limited now.
- **Breaking:** Emoji API: changed methods and renamed routes.
- MastodonAPI: Allow removal of avatar, banner and background.
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
- Mastodon API: On deletion, returns the original post text.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity.
- **Breaking:** Notification Settings API for suppressing notifications
has been simplified down to `block_from_strangers`.
- **Breaking:** Notification Settings API option for hiding push notification
@ -36,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added
- Chats: Added `accepts_chat_messages` field to user, exposed in APIs and federation.
- Chats: Added support for federated chats. For details, see the docs.
- ActivityPub: Added support for existing AP ids for instances migrated from Mastodon.
- Instance: Add `background_image` to configuration and `/api/v1/instance`
@ -52,14 +60,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Added `:reject_deletes` group to SimplePolicy
- MRF (`EmojiStealPolicy`): New MRF Policy which allows to automatically download emojis from remote instances
- Support pagination in emoji packs API (for packs and for files in pack)
- Support for viewing instances favicons next to posts and accounts
- Added Pleroma.Upload.Filter.Exiftool as an alternate EXIF stripping mechanism targeting GPS/location metadata.
<details>
<summary>API Changes</summary>
- Mastodon API: Add pleroma.parents_visible field to statuses.
- Mastodon API: Extended `/api/v1/instance`.
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
- Mastodon API: Add support for filtering replies in public and home timelines
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`
- Mastodon API: Add support for filtering replies in public and home timelines.
- Mastodon API: Support for `bot` field in `/api/v1/accounts/update_credentials`.
- Mastodon API: Support irreversible property for filters.
- Mastodon API: Add pleroma.favicon field to accounts.
- Admin API: endpoints for create/update/delete OAuth Apps.
- Admin API: endpoint for status view.
- OTP: Add command to reload emoji packs
@ -73,6 +86,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Resolving Peertube accounts with Webfinger
- `blob:` urls not being allowed by connect-src CSP
- Mastodon API: fix `GET /api/v1/notifications` not returning the full result set
- Rich Media Previews for Twitter links
- Admin API: fix `GET /api/pleroma/admin/users/:nickname/credentials` returning 404 when getting the credentials of a remote user while `:instance, :limit_to_local_content` is set to `:unauthenticated`
- Fix CSP policy generation to include remote Captcha services
- Fix edge case where MediaProxy truncates media, usually caused when Caddy is serving content for the other Federated instance.
## [Unreleased (patch)]
@ -214,7 +231,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: `pleroma.thread_muted` to the Status entity
- Mastodon API: Mark the direct conversation as read for the author when they send a new direct message
- Mastodon API, streaming: Add `pleroma.direct_conversation_id` to the `conversation` stream event payload.
- Mastodon API: Add `pleroma.unread_count` to the Marker entity
- Admin API: Render whole status in grouped reports
- Mastodon API: User timelines will now respect blocks, unless you are getting the user timeline of somebody you blocked (which would be empty otherwise).
- Mastodon API: Favoriting / Repeating a post multiple times will now return the identical response every time. Before, executing that action twice would return an error ("already favorited") on the second try.

View file

@ -33,7 +33,7 @@ ARG DATA=/var/lib/pleroma
RUN echo "http://nl.alpinelinux.org/alpine/latest-stable/community" >> /etc/apk/repositories &&\
apk update &&\
apk add imagemagick ncurses postgresql-client &&\
apk add exiftool imagemagick ncurses postgresql-client &&\
adduser --system --shell /bin/false --home ${HOME} pleroma &&\
mkdir -p ${DATA}/uploads &&\
mkdir -p ${DATA}/static &&\

View file

@ -24,6 +24,7 @@ defmodule Pleroma.LoadTesting.Activities do
@visibility ~w(public private direct unlisted)
@types [
:simple,
:simple_filtered,
:emoji,
:mentions,
:hell_thread,
@ -242,6 +243,15 @@ defp insert_activity(:simple, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status")
end
defp insert_activity(:simple_filtered, visibility, group, users, _opts)
when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status which must be filtered")
end
defp insert_activity(:simple_filtered, visibility, group, users, _opts) do
insert_local_activity(visibility, group, users, "Simple status which must be filtered")
end
defp insert_activity(:emoji, visibility, group, users, _opts)
when group in @remote_groups do
insert_remote_activity(visibility, group, users, "Remote status with emoji :firefox:")

View file

@ -32,10 +32,22 @@ defp fetch_user(user) do
)
end
defp create_filter(user) do
Pleroma.Filter.create(%Pleroma.Filter{
user_id: user.id,
phrase: "must be filtered",
hide: true
})
end
defp delete_filter(filter), do: Repo.delete(filter)
defp fetch_timelines(user) do
fetch_home_timeline(user)
fetch_home_timeline_with_filter(user)
fetch_direct_timeline(user)
fetch_public_timeline(user)
fetch_public_timeline_with_filter(user)
fetch_public_timeline(user, :with_blocks)
fetch_public_timeline(user, :local)
fetch_public_timeline(user, :tag)
@ -61,7 +73,7 @@ defp opts_for_home_timeline(user) do
}
end
defp fetch_home_timeline(user) do
defp fetch_home_timeline(user, title_end \\ "") do
opts = opts_for_home_timeline(user)
recipients = [user.ap_id | User.following(user)]
@ -84,9 +96,11 @@ defp fetch_home_timeline(user) do
|> Enum.reverse()
|> List.last()
title = "home timeline " <> title_end
Benchee.run(
%{
"home timeline" => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
title => fn opts -> ActivityPub.fetch_activities(recipients, opts) end
},
inputs: %{
"1 page" => opts,
@ -108,6 +122,14 @@ defp fetch_home_timeline(user) do
)
end
defp fetch_home_timeline_with_filter(user) do
{:ok, filter} = create_filter(user)
fetch_home_timeline(user, "with filters")
delete_filter(filter)
end
defp opts_for_direct_timeline(user) do
%{
visibility: "direct",
@ -210,6 +232,14 @@ defp fetch_public_timeline(user) do
fetch_public_timeline(opts, "public timeline")
end
defp fetch_public_timeline_with_filter(user) do
{:ok, filter} = create_filter(user)
opts = opts_for_public_timeline(user)
fetch_public_timeline(opts, "public timeline with filters")
delete_filter(filter)
end
defp fetch_public_timeline(user, :local) do
opts = opts_for_public_timeline(user, :local)

View file

@ -97,6 +97,7 @@
"dat",
"dweb",
"gopher",
"hyper",
"ipfs",
"ipns",
"irc",
@ -188,6 +189,7 @@
background_image: "/images/city.jpg",
instance_thumbnail: "/instance/thumbnail.jpeg",
limit: 5_000,
description_limit: 5_000,
chat_limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000,
@ -436,10 +438,7 @@
config :pleroma, Pleroma.Web.Preload,
providers: [
Pleroma.Web.Preload.Providers.Instance,
Pleroma.Web.Preload.Providers.User,
Pleroma.Web.Preload.Providers.Timelines,
Pleroma.Web.Preload.Providers.StatusNet
Pleroma.Web.Preload.Providers.Instance
]
config :pleroma, :http_security,
@ -499,8 +498,7 @@
config :pleroma, Oban,
repo: Pleroma.Repo,
verbose: false,
prune: {:maxlen, 1500},
log: false,
queues: [
activity_expiration: 10,
federator_incoming: 50,
@ -707,6 +705,8 @@
config :ex_aws, http_client: Pleroma.HTTP.ExAws
config :pleroma, :instances_favicons, enabled: false
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

View file

@ -23,29 +23,26 @@
key: :uploader,
type: :module,
description: "Module which will be used for uploads",
suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3]
suggestions: {:list_behaviour_implementations, Pleroma.Uploaders.Uploader}
},
%{
key: :filters,
type: {:list, :module},
description:
"List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
suggestions:
Generator.list_modules_in_dir(
"lib/pleroma/upload/filter",
"Elixir.Pleroma.Upload.Filter."
)
suggestions: {:list_behaviour_implementations, Pleroma.Upload.Filter}
},
%{
key: :link_name,
type: :boolean,
description:
"If enabled, a name parameter will be added to the url of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
"If enabled, a name parameter will be added to the URL of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
},
%{
key: :base_url,
label: "Base URL",
type: :string,
description: "Base url for the uploads, needed if you use CDN",
description: "Base URL for the uploads, needed if you use CDN",
suggestions: [
"https://cdn-host.com"
]
@ -58,6 +55,7 @@
},
%{
key: :proxy_opts,
label: "Proxy Options",
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
suggestions: [
@ -85,6 +83,7 @@
},
%{
key: :http,
label: "HTTP",
type: :keyword,
description: "HTTP options",
children: [
@ -193,7 +192,9 @@
%{
key: :args,
type: [:string, {:list, :string}, {:list, :tuple}],
description: "List of actions for the mogrify command",
description:
"List of actions for the mogrify command. It's possible to add self-written settings as string. " <>
"For example `[\"auto-orient\", \"strip\", {\"resize\", \"3840x1080>\"}]` string will be parsed into list of the settings.",
suggestions: [
"strip",
"auto-orient",
@ -479,6 +480,7 @@
%{
group: :pleroma,
key: :uri_schemes,
label: "URI Schemes",
type: :group,
description: "URI schemes related settings",
children: [
@ -492,6 +494,7 @@
"dat",
"dweb",
"gopher",
"hyper",
"ipfs",
"ipns",
"irc",
@ -651,17 +654,17 @@
key: :invites_enabled,
type: :boolean,
description:
"Enable user invitations for admins (depends on `registrations_open` being disabled)."
"Enable user invitations for admins (depends on `registrations_open` being disabled)"
},
%{
key: :account_activation_required,
type: :boolean,
description: "Require users to confirm their emails before signing in."
description: "Require users to confirm their emails before signing in"
},
%{
key: :federating,
type: :boolean,
description: "Enable federation with other instances."
description: "Enable federation with other instances"
},
%{
key: :federation_incoming_replies_max_depth,
@ -679,7 +682,7 @@
label: "Fed. reachability timeout days",
type: :integer,
description:
"Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.",
"Timeout (in days) of each external federation target being unreachable prior to pausing federating to it",
suggestions: [
7
]
@ -693,8 +696,9 @@
key: :public,
type: :boolean,
description:
"Makes the client API in authentificated mode-only except for user-profiles." <>
" Useful for disabling the Local Timeline and The Whole Known Network."
"Makes the client API in authenticated mode-only except for user-profiles." <>
" Useful for disabling the Local Timeline and The Whole Known Network. " <>
" Note: when setting to `false`, please also check `:restrict_unauthenticated` setting."
},
%{
key: :quarantined_instances,
@ -801,6 +805,7 @@
},
%{
key: :safe_dm_mentions,
label: "Safe DM mentions",
type: :boolean,
description:
"If enabled, only mentions at the beginning of a post will be used to address people in direct messages." <>
@ -840,7 +845,7 @@
%{
key: :skip_thread_containment,
type: :boolean,
description: "Skip filtering out broken threads. Default: enabled"
description: "Skip filtering out broken threads. Default: enabled."
},
%{
key: :limit_to_local_content,
@ -904,6 +909,7 @@
children: [
%{
key: :totp,
label: "TOTP settings",
type: :keyword,
description: "TOTP settings",
suggestions: [digits: 6, period: 30],
@ -920,7 +926,7 @@
type: :integer,
suggestions: [30],
description:
"a period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
"A period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
}
]
},
@ -934,7 +940,7 @@
key: :number,
type: :integer,
suggestions: [5],
description: "number of backup codes to generate."
description: "Number of backup codes to generate."
},
%{
key: :length,
@ -974,6 +980,7 @@
group: :logger,
type: :group,
key: :ex_syslogger,
label: "ExSyslogger",
description: "ExSyslogger-related settings",
children: [
%{
@ -992,7 +999,7 @@
%{
key: :format,
type: :string,
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
suggestions: ["$metadata[$level] $message"]
},
%{
@ -1006,6 +1013,7 @@
group: :logger,
type: :group,
key: :console,
label: "Console Logger",
description: "Console logger settings",
children: [
%{
@ -1017,7 +1025,7 @@
%{
key: :format,
type: :string,
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
suggestions: ["$metadata[$level] $message"]
},
%{
@ -1030,6 +1038,7 @@
%{
group: :quack,
type: :group,
label: "Quack Logger",
description: "Quack-related settings",
children: [
%{
@ -1058,6 +1067,7 @@
},
%{
key: :webhook_url,
label: "Webhook URL",
type: :string,
description: "Configure the Slack incoming webhook",
suggestions: ["https://hooks.slack.com/services/YOUR-KEY-HERE"]
@ -1140,19 +1150,19 @@
key: :greentext,
label: "Greentext",
type: :boolean,
description: "Enables green text on lines prefixed with the > character."
description: "Enables green text on lines prefixed with the > character"
},
%{
key: :hideFilteredStatuses,
label: "Hide Filtered Statuses",
type: :boolean,
description: "Hides filtered statuses from timelines."
description: "Hides filtered statuses from timelines"
},
%{
key: :hideMutedPosts,
label: "Hide Muted Posts",
type: :boolean,
description: "Hides muted statuses from timelines."
description: "Hides muted statuses from timelines"
},
%{
key: :hidePostStats,
@ -1164,7 +1174,7 @@
key: :hideSitename,
label: "Hide Sitename",
type: :boolean,
description: "Hides instance name from PleromaFE banner."
description: "Hides instance name from PleromaFE banner"
},
%{
key: :hideUserStats,
@ -1209,14 +1219,14 @@
label: "NSFW Censor Image",
type: :string,
description:
"URL of the image to use for hiding NSFW media attachments in the timeline.",
"URL of the image to use for hiding NSFW media attachments in the timeline",
suggestions: ["/static/img/nsfw.74818f9.png"]
},
%{
key: :postContentType,
label: "Post Content Type",
type: {:dropdown, :atom},
description: "Default post formatting option.",
description: "Default post formatting option",
suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
},
%{
@ -1245,14 +1255,14 @@
key: :sidebarRight,
label: "Sidebar on Right",
type: :boolean,
description: "Change alignment of sidebar and panels to the right."
description: "Change alignment of sidebar and panels to the right"
},
%{
key: :showFeaturesPanel,
label: "Show instance features panel",
type: :boolean,
description:
"Enables panel displaying functionality of the instance on the About page."
"Enables panel displaying functionality of the instance on the About page"
},
%{
key: :showInstanceSpecificPanel,
@ -1310,7 +1320,7 @@
key: :mascots,
type: {:keyword, :map},
description:
"Keyword of mascots, each element must contain both an url and a mime_type key",
"Keyword of mascots, each element must contain both an URL and a mime_type key",
suggestions: [
pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png",
@ -1334,7 +1344,7 @@
%{
key: :default_user_avatar,
type: :string,
description: "URL of the default user avatar.",
description: "URL of the default user avatar",
suggestions: ["/images/avi.png"]
}
]
@ -1344,7 +1354,7 @@
key: :manifest,
type: :group,
description:
"This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE",
"This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE.",
children: [
%{
key: :icons,
@ -1380,10 +1390,45 @@
},
%{
group: :pleroma,
key: :mrf_simple,
label: "MRF simple",
key: :mrf,
tab: :mrf,
label: "MRF",
type: :group,
description: "Message Rewrite Facility",
description: "General MRF settings",
children: [
%{
key: :policies,
type: [:module, {:list, :module}],
description:
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
},
%{
key: :transparency,
label: "MRF transparency",
type: :boolean,
description:
"Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
},
%{
key: :transparency_exclusions,
label: "MRF transparency exclusions",
type: {:list, :string},
description:
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
suggestions: [
"exclusion.com"
]
}
]
},
%{
group: :pleroma,
key: :mrf_simple,
tab: :mrf,
label: "MRF Simple",
type: :group,
description: "Simple ingress policies",
children: [
%{
key: :media_removal,
@ -1402,7 +1447,7 @@
key: :federated_timeline_removal,
type: {:list, :string},
description:
"List of instances to remove from Federated (aka The Whole Known Network) Timeline",
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
suggestions: ["example.com", "*.example.com"]
},
%{
@ -1446,14 +1491,15 @@
%{
group: :pleroma,
key: :mrf_activity_expiration,
tab: :mrf,
label: "MRF Activity Expiration Policy",
type: :group,
description: "Adds expiration to all local Create Note activities",
description: "Adds automatic expiration to all local activities",
children: [
%{
key: :days,
type: :integer,
description: "Default global expiration time for all local Create activities (in days)",
description: "Default global expiration time for all local activities (in days)",
suggestions: [90, 365]
}
]
@ -1461,7 +1507,8 @@
%{
group: :pleroma,
key: :mrf_subchain,
label: "MRF subchain",
tab: :mrf,
label: "MRF Subchain",
type: :group,
description:
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
@ -1469,7 +1516,7 @@
children: [
%{
key: :match_actor,
type: :map,
type: {:map, {:list, :string}},
description: "Matches a series of regular expressions against the actor field",
suggestions: [
%{
@ -1482,9 +1529,9 @@
%{
group: :pleroma,
key: :mrf_rejectnonpublic,
description:
"MRF RejectNonPublic settings. RejectNonPublic drops posts with non-public visibility settings.",
label: "MRF reject non public",
tab: :mrf,
description: "RejectNonPublic drops posts with non-public visibility settings.",
label: "MRF Reject Non Public",
type: :group,
children: [
%{
@ -1503,16 +1550,17 @@
%{
group: :pleroma,
key: :mrf_hellthread,
label: "MRF hellthread",
tab: :mrf,
label: "MRF Hellthread",
type: :group,
description: "Block messages with too much mentions",
description: "Block messages with excessive user mentions",
children: [
%{
key: :delist_threshold,
type: :integer,
description:
"Number of mentioned users after which the message gets delisted (the message can still be seen, " <>
" but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.",
"Number of mentioned users after which the message gets removed from timelines and" <>
"disables notifications. Set to 0 to disable.",
suggestions: [10]
},
%{
@ -1527,27 +1575,28 @@
%{
group: :pleroma,
key: :mrf_keyword,
label: "MRF keyword",
tab: :mrf,
label: "MRF Keyword",
type: :group,
description: "Reject or Word-Replace messages with a keyword or regex",
children: [
%{
key: :reject,
type: [:string, :regex],
type: {:list, :string},
description:
"A list of patterns which result in message being rejected. Each pattern can be a string or a regular expression.",
suggestions: ["foo", ~r/foo/iu]
},
%{
key: :federated_timeline_removal,
type: [:string, :regex],
type: {:list, :string},
description:
"A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or a regular expression.",
suggestions: ["foo", ~r/foo/iu]
},
%{
key: :replace,
type: [{:tuple, :string, :string}, {:tuple, :regex, :string}],
type: {:list, :tuple},
description:
"A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
@ -1557,14 +1606,15 @@
%{
group: :pleroma,
key: :mrf_mention,
label: "MRF mention",
tab: :mrf,
label: "MRF Mention",
type: :group,
description: "Block messages which mention a user",
description: "Block messages which mention a specific user",
children: [
%{
key: :actors,
type: {:list, :string},
description: "A list of actors for which any post mentioning them will be dropped.",
description: "A list of actors for which any post mentioning them will be dropped",
suggestions: ["actor1", "actor2"]
}
]
@ -1572,7 +1622,8 @@
%{
group: :pleroma,
key: :mrf_vocabulary,
label: "MRF vocabulary",
tab: :mrf,
label: "MRF Vocabulary",
type: :group,
description: "Filter messages which belong to certain activity vocabularies",
children: [
@ -1580,14 +1631,14 @@
key: :accept,
type: {:list, :string},
description:
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted",
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
},
%{
key: :reject,
type: {:list, :string},
description:
"A list of ActivityStreams terms to reject. If empty, no messages are rejected",
"A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
}
]
@ -1617,6 +1668,7 @@
},
%{
key: :base_url,
label: "Base URL",
type: :string,
description:
"The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
@ -1649,6 +1701,7 @@
},
%{
key: :proxy_opts,
label: "Proxy Options",
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
suggestions: [
@ -1676,6 +1729,7 @@
},
%{
key: :http,
label: "HTTP",
type: :keyword,
description: "HTTP options",
children: [
@ -1732,15 +1786,20 @@
},
%{
key: :headers,
type: {:list, :tuple},
description: "HTTP headers of request.",
type: {:keyword, :string},
description: "HTTP headers of request",
suggestions: [{"x-refresh", 1}]
},
%{
key: :options,
type: :keyword,
description: "Request options.",
suggestions: [params: %{ts: "xxx"}]
description: "Request options",
children: [
%{
key: :params,
type: {:map, :string}
}
]
}
]
},
@ -1771,6 +1830,7 @@
},
%{
key: :ip,
label: "IP",
type: :tuple,
description: "IP address to bind to",
suggestions: [{0, 0, 0, 0}]
@ -1784,7 +1844,7 @@
%{
key: :dstport,
type: :integer,
description: "Port advertised in urls (optional, defaults to port)",
description: "Port advertised in URLs (optional, defaults to port)",
suggestions: [9999]
}
]
@ -1792,6 +1852,7 @@
%{
group: :pleroma,
key: :activitypub,
label: "ActivityPub",
type: :group,
description: "ActivityPub-related settings",
children: [
@ -1814,7 +1875,7 @@
key: :note_replies_output_limit,
type: :integer,
description:
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)."
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)"
},
%{
key: :follow_handshake_timeout,
@ -1827,6 +1888,7 @@
%{
group: :pleroma,
key: :http_security,
label: "HTTP security",
type: :group,
description: "HTTP security settings",
children: [
@ -1865,7 +1927,7 @@
key: :report_uri,
label: "Report URI",
type: :string,
description: "Adds the specified url to report-uri and report-to group in CSP header",
description: "Adds the specified URL to report-uri and report-to group in CSP header",
suggestions: ["https://example.com/report-uri"]
}
]
@ -1873,9 +1935,10 @@
%{
group: :web_push_encryption,
key: :vapid_details,
label: "Vapid Details",
type: :group,
description:
"Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it",
"Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it.",
children: [
%{
key: :subject,
@ -1942,6 +2005,7 @@
},
%{
group: :pleroma,
label: "Pleroma Admin Token",
type: :group,
description:
"Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter",
@ -1949,7 +2013,7 @@
%{
key: :admin_token,
type: :string,
description: "Token",
description: "Admin token",
suggestions: ["We recommend a secure random string or UUID"]
}
]
@ -1968,18 +2032,11 @@
""",
children: [
%{
key: :verbose,
key: :log,
type: {:dropdown, :atom},
description: "Logs verbose mode",
suggestions: [false, :error, :warn, :info, :debug]
},
%{
key: :prune,
type: [:atom, :tuple],
description:
"Non-retryable jobs [pruning settings](https://github.com/sorentwo/oban#pruning)",
suggestions: [:disabled, {:maxlen, 1500}, {:maxage, 60 * 60}]
},
%{
key: :queues,
type: {:keyword, :integer},
@ -2114,24 +2171,24 @@
key: :rich_media,
type: :group,
description:
"If enabled the instance will parse metadata from attached links to generate link previews.",
"If enabled the instance will parse metadata from attached links to generate link previews",
children: [
%{
key: :enabled,
type: :boolean,
description: "Enables RichMedia parsing of URLs."
description: "Enables RichMedia parsing of URLs"
},
%{
key: :ignore_hosts,
type: {:list, :string},
description: "List of hosts which will be ignored by the metadata parser.",
description: "List of hosts which will be ignored by the metadata parser",
suggestions: ["accounts.google.com", "xss.website"]
},
%{
key: :ignore_tld,
label: "Ignore TLD",
type: {:list, :string},
description: "List TLDs (top-level domains) which will ignore for parse metadata.",
description: "List TLDs (top-level domains) which will ignore for parse metadata",
suggestions: ["local", "localdomain", "lan"]
},
%{
@ -2159,31 +2216,32 @@
%{
group: :auto_linker,
key: :opts,
label: "Auto Linker",
type: :group,
description: "Configuration for the auto_linker library",
children: [
%{
key: :class,
type: [:string, false],
description: "Specify the class to be added to the generated link. Disable to clear",
description: "Specify the class to be added to the generated link. Disable to clear.",
suggestions: ["auto-linker", false]
},
%{
key: :rel,
type: [:string, false],
description: "Override the rel attribute. Disable to clear",
description: "Override the rel attribute. Disable to clear.",
suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,
type: :boolean,
description: "Link urls will open in new window/tab"
description: "Link URLs will open in new window/tab"
},
%{
key: :truncate,
type: [:integer, false],
description:
"Set to a number to truncate urls longer then the number. Truncated urls will end in `..`",
"Set to a number to truncate URLs longer then the number. Truncated URLs will end in `..`",
suggestions: [15, false]
},
%{
@ -2194,7 +2252,7 @@
%{
key: :extra,
type: :boolean,
description: "Link urls with rarely used schemes (magnet, ipfs, irc, etc.)"
description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
}
]
},
@ -2240,6 +2298,7 @@
},
%{
group: :pleroma,
label: "Pleroma Authenticator",
type: :group,
description: "Authenticator",
children: [
@ -2253,6 +2312,7 @@
%{
group: :pleroma,
key: :ldap,
label: "LDAP",
type: :group,
description:
"Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
@ -2339,6 +2399,7 @@
},
%{
key: :uid,
label: "UID",
type: :string,
description:
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
@ -2354,11 +2415,12 @@
children: [
%{
key: :enforce_oauth_admin_scope_usage,
label: "Enforce OAuth admin scope usage",
type: :boolean,
description:
"OAuth admin scope requirement toggle. " <>
"If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
"(client app must support admin scopes). If disabled and token doesn't have admin scope(s)," <>
"(client app must support admin scopes). If disabled and token doesn't have admin scope(s), " <>
"`is_admin` user flag grants access to admin-specific actions."
},
%{
@ -2370,6 +2432,7 @@
},
%{
key: :oauth_consumer_template,
label: "OAuth consumer template",
type: :string,
description:
"OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
@ -2378,6 +2441,7 @@
},
%{
key: :oauth_consumer_strategies,
label: "OAuth consumer strategies",
type: {:list, :string},
description:
"The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
@ -2451,7 +2515,7 @@
%{
key: :styling,
type: :map,
description: "a map with color settings for email templates.",
description: "A map with color settings for email templates.",
suggestions: [
%{
link_color: "#d8a070",
@ -2506,14 +2570,14 @@
%{
key: :enabled,
type: :boolean,
description: "enables new users admin digest email when `true`",
suggestions: [false]
description: "Enables new users admin digest email when `true`"
}
]
},
%{
group: :pleroma,
key: :oauth2,
label: "OAuth2",
type: :group,
description: "Configure OAuth 2 provider capabilities",
children: [
@ -2532,7 +2596,7 @@
%{
key: :clean_expired_tokens,
type: :boolean,
description: "Enable a background job to clean expired oauth tokens. Default: disabled."
description: "Enable a background job to clean expired OAuth tokens. Default: disabled."
}
]
},
@ -2556,7 +2620,7 @@
},
%{
key: :groups,
type: {:keyword, :string, {:list, :string}},
type: {:keyword, {:list, :string}},
description:
"Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <>
" and the value is the location or array of locations. * can be used as a wildcard.",
@ -2616,6 +2680,7 @@
},
%{
key: :relation_id_action,
label: "Relation ID action",
type: [:tuple, {:list, :tuple}],
description: "For actions on relation with a specific user (follow, unfollow)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
@ -2629,6 +2694,7 @@
},
%{
key: :status_id_action,
label: "Status ID action",
type: [:tuple, {:list, :tuple}],
description:
"For fav / unfav or reblog / unreblog actions on the same status by the same user",
@ -2644,6 +2710,7 @@
},
%{
group: :esshd,
label: "ESSHD",
type: :group,
description:
"Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <>
@ -2682,8 +2749,9 @@
},
%{
group: :mime,
label: "Mime Types",
type: :group,
description: "Mime types",
description: "Mime Types settings",
children: [
%{
key: :types,
@ -2742,6 +2810,7 @@
%{
group: :pleroma,
key: :http,
label: "HTTP",
type: :group,
description: "HTTP settings",
children: [
@ -2790,6 +2859,7 @@
%{
group: :pleroma,
key: :markup,
label: "Markup Settings",
type: :group,
children: [
%{
@ -2830,8 +2900,9 @@
},
%{
group: :pleroma,
tab: :mrf,
key: :mrf_normalize_markup,
label: "MRF normalize markup",
label: "MRF Normalize Markup",
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
type: :group,
children: [
@ -2887,6 +2958,7 @@
},
%{
group: :cors_plug,
label: "CORS plug config",
type: :group,
children: [
%{
@ -2959,6 +3031,7 @@
%{
group: :pleroma,
key: :web_cache_ttl,
label: "Web cache TTL",
type: :group,
description:
"The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
@ -2981,9 +3054,10 @@
%{
group: :pleroma,
key: :static_fe,
label: "Static FE",
type: :group,
description:
"Render profiles and posts using server-generated HTML that is viewable without using JavaScript.",
"Render profiles and posts using server-generated HTML that is viewable without using JavaScript",
children: [
%{
key: :enabled,
@ -3001,18 +3075,18 @@
%{
key: :post_title,
type: :map,
description: "Configure title rendering.",
description: "Configure title rendering",
children: [
%{
key: :max_length,
type: :integer,
description: "Maximum number of characters before truncating title.",
description: "Maximum number of characters before truncating title",
suggestions: [100]
},
%{
key: :omission,
type: :string,
description: "Replacement which will be used after truncating string.",
description: "Replacement which will be used after truncating string",
suggestions: ["..."]
}
]
@ -3022,8 +3096,11 @@
%{
group: :pleroma,
key: :mrf_object_age,
label: "MRF Object Age",
tab: :mrf,
type: :group,
description: "Rejects or delists posts based on their age when received.",
description:
"Rejects or delists posts based on their timestamp deviance from your server's clock.",
children: [
%{
key: :threshold,
@ -3036,7 +3113,7 @@
type: {:list, :atom},
description:
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
"`:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines; " <>
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
"`:reject` rejects the message entirely",
suggestions: [:delist, :strip_followers, :reject]
}
@ -3064,13 +3141,13 @@
%{
key: :workers,
type: :integer,
description: "Number of workers to send notifications.",
description: "Number of workers to send notifications",
suggestions: [3]
},
%{
key: :overflow_workers,
type: :integer,
description: "Maximum number of workers created if pool is empty.",
description: "Maximum number of workers created if pool is empty",
suggestions: [2]
}
]
@ -3357,44 +3434,46 @@
key: :strict,
type: :boolean,
description:
"Enables strict input validation (useful in development, not recommended in production)",
suggestions: [false]
"Enables strict input validation (useful in development, not recommended in production)"
}
]
},
%{
group: :pleroma,
key: :mrf,
key: :instances_favicons,
type: :group,
description: "General MRF settings",
description: "Control favicons for instances",
children: [
%{
key: :policies,
type: [:module, {:list, :module}],
description:
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions:
Generator.list_modules_in_dir(
"lib/pleroma/web/activity_pub/mrf",
"Elixir.Pleroma.Web.ActivityPub.MRF."
)
},
%{
key: :transparency,
label: "MRF transparency",
key: :enabled,
type: :boolean,
description:
"Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
description: "Allow/disallow displaying and getting instances favicons"
}
]
},
%{
group: :ex_aws,
key: :s3,
type: :group,
descriptions: "S3 service related settings",
children: [
%{
key: :access_key_id,
type: :string,
description: "S3 access key ID",
suggestions: ["AKIAQ8UKHTGIYN7DMWWJ"]
},
%{
key: :transparency_exclusions,
label: "MRF transparency exclusions",
type: {:list, :string},
description:
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
suggestions: [
"exclusion.com"
]
key: :secret_access_key,
type: :string,
description: "Secret access key",
suggestions: ["JFGt+fgH1UQ7vLUQjpW+WvjTdV/UNzVxcwn7DkaeFKtBS5LvoXvIiME4NQBsT6ZZ"]
},
%{
key: :host,
type: :string,
description: "S3 host",
suggestions: ["s3.eu-central-1.amazonaws.com"]
}
]
}

View file

@ -79,8 +79,8 @@
config :pleroma, Oban,
queues: false,
prune: :disabled,
crontab: false
crontab: false,
plugins: false
config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 2,
@ -111,6 +111,8 @@
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
config :pleroma, :instances_favicons, enabled: true
if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs"
else

View file

@ -27,6 +27,7 @@ Has these additional fields under the `pleroma` object:
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
- `thread_muted`: true if the thread the post belongs to is muted
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
- `parent_visible`: If the parent of this post is visible to the user or not.
## Media Attachments
@ -51,21 +52,27 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
Has these additional fields under the `pleroma` object:
- `ap_id`: nullable URL string, ActivityPub id of the user
- `background_image`: nullable URL string, background image of the user
- `tags`: Lists an array of tags for the user
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
- `relationship` (object): Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
- `is_moderator`: boolean, nullable, true if user is a moderator
- `is_admin`: boolean, nullable, true if user is an admin
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
- `hide_followers`: boolean, true when the user has follower hiding enabled
- `hide_follows`: boolean, true when the user has follow hiding enabled
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
- `hide_follows_count`: boolean, true when the user has follow stat hiding enabled
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `/api/v1/accounts/verify_credentials` and `/api/v1/accounts/update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `/api/v1/accounts/verify_credentials`
- `deactivated`: boolean, true when the user is deactivated
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
- `favicon`: nullable URL string, Favicon image of the user's instance
### Source
@ -162,7 +169,7 @@ Returns: array of Status.
The maximum number of statuses is limited to 100 per request.
## PATCH `/api/v1/update_credentials`
## PATCH `/api/v1/accounts/update_credentials`
Additional parameters can be added to the JSON body/Form data:
@ -177,9 +184,12 @@ Additional parameters can be added to the JSON body/Form data:
- `pleroma_settings_store` - Opaque user settings to be saved on the backend.
- `skip_thread_containment` - if true, skip filtering out broken threads
- `allow_following_move` - if true, allows automatically follow moved following accounts
- `pleroma_background_image` - sets the background image of the user.
- `pleroma_background_image` - sets the background image of the user. Can be set to "" (an empty string) to reset.
- `discoverable` - if true, discovery of this account in search results and other services is allowed.
- `actor_type` - the type of this account.
- `accepts_chat_messages` - if false, this account will reject all chat messages.
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
### Pleroma Settings Store
@ -187,7 +197,7 @@ Pleroma has mechanism that allows frontends to save blobs of json for each user
The parameter should have a form of `{frontend_name: {...}}`, with `frontend_name` identifying your type of client, e.g. `pleroma_fe`. It will overwrite everything under this property, but will not overwrite other frontend's settings.
This information is returned in the `verify_credentials` endpoint.
This information is returned in the `/api/v1/accounts/verify_credentials` endpoint.
## Authentication
@ -215,6 +225,8 @@ Has theses additional parameters (which are the same as in Pleroma-API):
`GET /api/v1/instance` has additional fields
- `max_toot_chars`: The maximum characters per post
- `chat_limit`: The maximum characters per chat message
- `description_limit`: The maximum characters per image description
- `poll_limits`: The limits of polls
- `upload_limit`: The maximum upload file size
- `avatar_upload_limit`: The same for avatars
@ -223,6 +235,7 @@ Has theses additional parameters (which are the same as in Pleroma-API):
- `background_image`: A background image that frontends can use
- `pleroma.metadata.features`: A list of supported features
- `pleroma.metadata.federation`: The federation restrictions of this instance
- `pleroma.metadata.fields_limits`: A list of values detailing the length and count limitation for various instance-configurable fields.
- `vapid_public_key`: The public key needed for push messages
## Markers

View file

@ -57,11 +57,11 @@ mix pleroma.user invites
## Revoke invite
```sh tab="OTP"
./bin/pleroma_ctl user revoke_invite <token_or_id>
./bin/pleroma_ctl user revoke_invite <token>
```
```sh tab="From Source"
mix pleroma.user revoke_invite <token_or_id>
mix pleroma.user revoke_invite <token>
```

View file

@ -1,6 +1,6 @@
# Updating your instance
You should **always check the release notes/changelog** in case there are config deprecations, special update special update steps, etc.
You should **always check the [release notes/changelog](https://git.pleroma.social/pleroma/pleroma/-/releases)** in case there are config deprecations, special update steps, etc.
Besides that, doing the following is generally enough:

View file

@ -18,6 +18,7 @@ To add configuration to your config file, you can copy it from the base config.
* `notify_email`: Email used for notifications.
* `description`: The instances description, can be seen in nodeinfo and ``/api/v1/instance``.
* `limit`: Posts character limit (CW/Subject included in the counter).
* `discription_limit`: The character limit for image descriptions.
* `chat_limit`: Character limit of the instance chat messages.
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
* `upload_limit`: File size limit of uploads (except for avatar, background, banner).
@ -36,7 +37,7 @@ To add configuration to your config file, you can copy it from the base config.
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Enable Pleromas Relay, which makes it possible to follow a whole instance.
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. See also: `restrict_unauthenticated`.
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
@ -154,7 +155,7 @@ config :pleroma, :mrf_user_allowlist, %{
* `:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines
* `:reject` rejects the message entirely
#### mrf_steal_emoji
#### :mrf_steal_emoji
* `hosts`: List of hosts to steal emojis from
* `rejected_shortcodes`: Regex-list of shortcodes to reject
* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk
@ -475,7 +476,6 @@ For each pool, the options are:
* `:timeout` - timeout while `gun` will wait for response
* `:max_overflow` - additional workers if pool is under load
## Captcha
### Pleroma.Captcha
@ -493,7 +493,7 @@ A built-in captcha provider. Enabled by default.
#### Pleroma.Captcha.Kocaptcha
Kocaptcha is a very simple captcha service with a single API endpoint,
the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint
the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). The default endpoint
`https://captcha.kotobank.ch` is hosted by the developer.
* `endpoint`: the Kocaptcha endpoint to use.
@ -501,6 +501,7 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end
## Uploads
### Pleroma.Upload
* `uploader`: Which one of the [uploaders](#uploaders) to use.
* `filters`: List of [upload filters](#upload-filters) to use.
* `link_name`: When enabled Pleroma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe`
@ -513,10 +514,15 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end
`strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
### Uploaders
#### Pleroma.Uploaders.Local
* `uploads`: Which directory to store the user-uploads in, relative to pleromas working directory.
#### Pleroma.Uploaders.S3
Don't forget to configure [Ex AWS S3](#ex-aws-s3-settings)
* `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")
@ -525,17 +531,23 @@ For example, when using CDN to S3 virtual host format, set "".
At this time, write CNAME to CDN in public_endpoint.
* `streaming_enabled`: Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems.
#### Ex AWS S3 settings
* `access_key_id`: Access key ID
* `secret_access_key`: Secret access key
* `host`: S3 host
Example:
```elixir
config :ex_aws, :s3,
access_key_id: "xxxxxxxxxx",
secret_access_key: "yyyyyyyyyy",
host: "s3.eu-central-1.amazonaws.com"
```
### Upload filters
#### Pleroma.Upload.Filter.Mogrify
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
#### Pleroma.Upload.Filter.Dedupe
No specific configuration.
#### Pleroma.Upload.Filter.AnonymizeFilename
This filter replaces the filename (not the path) of an upload. For complete obfuscation, add
@ -543,6 +555,20 @@ This filter replaces the filename (not the path) of an upload. For complete obfu
* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`.
#### Pleroma.Upload.Filter.Dedupe
No specific configuration.
#### Pleroma.Upload.Filter.Exiftool
This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact.
No specific configuration.
#### Pleroma.Upload.Filter.Mogrify
* `args`: List of actions for the `mogrify` command like `"strip"` or `["strip", "auto-orient", {"implode", "1"}]`.
## Email
### Pleroma.Emails.Mailer
@ -970,11 +996,11 @@ config :pleroma, :database_config_whitelist, [
### :restrict_unauthenticated
Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
Restrict access for unauthenticated users to timelines (public and federated), user profiles and statuses.
* `timelines`: public and federated timelines
* `local`: public timeline
* `federated`
* `federated`: federated timeline (includes public timeline)
* `profiles`: user profiles
* `local`
* `remote`
@ -982,7 +1008,14 @@ Restrict access for unauthenticated users to timelines (public and federate), us
* `local`
* `remote`
Note: setting `restrict_unauthenticated/timelines/local` to `true` has no practical sense if `restrict_unauthenticated/timelines/federated` is set to `false` (since local public activities will still be delivered to unauthenticated users as part of federated timeline).
## Pleroma.Web.ApiSpec.CastAndValidate
* `:strict` a boolean, enables strict input validation (useful in development, not recommended in production). Defaults to `false`.
## :instances_favicons
Control favicons for instances.
* `enabled`: Allow/disallow displaying and getting instances favicons

View file

@ -0,0 +1,151 @@
# How to activate Pleroma in-database configuration
## Explanation
The configuration of Pleroma has traditionally been managed with a config file, e.g. `config/prod.secret.exs`. This method requires a restart of the application for any configuration changes to take effect. We have made it possible to control most settings in the AdminFE interface after running a migration script.
## Migration to database config
1. Stop your Pleroma instance and edit your Pleroma config to enable database configuration:
```
config :pleroma, configurable_from_database: true
```
2. Run the mix task to migrate to the database. You'll receive some debugging output and a few messages informing you of what happened.
**Source:**
```
$ mix pleroma.config migrate_to_db
```
or
**OTP:**
```
$ ./bin/pleroma_ctl config migrate_to_db
```
```
10:04:34.155 [debug] QUERY OK source="config" db=1.6ms decode=2.0ms queue=33.5ms idle=0.0ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
Migrating settings from file: /home/pleroma/config/dev.secret.exs
10:04:34.240 [debug] QUERY OK db=4.5ms queue=0.3ms idle=92.2ms
TRUNCATE config; []
10:04:34.244 [debug] QUERY OK db=2.8ms queue=0.3ms idle=97.2ms
ALTER SEQUENCE config_id_seq RESTART; []
10:04:34.256 [debug] QUERY OK source="config" db=0.8ms queue=1.4ms idle=109.8ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 WHERE ((c0."group" = $1) AND (c0."key" = $2)) [":pleroma", ":instance"]
10:04:34.292 [debug] QUERY OK db=2.6ms queue=1.7ms idle=137.7ms
INSERT INTO "config" ("group","key","value","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5) RETURNING "id" [":pleroma", ":instance", <<131, 108, 0, 0, 0, 1, 104, 2, 100, 0, 4, 110, 97, 109, 101, 109, 0, 0, 0, 7, 66, 108, 101, 114, 111, 109, 97, 106>>, ~N[2020-07-12 15:04:34], ~N[2020-07-12 15:04:34]]
Settings for key instance migrated.
Settings for group :pleroma migrated.
```
3. It is recommended to backup your config file now.
```
cp config/dev.secret.exs config/dev.secret.exs.orig
```
4. Now you can edit your config file and strip it down to the only settings which are not possible to control in the database. e.g., the Postgres and webserver (Endpoint) settings cannot be controlled in the database because the application needs the settings to start up and access the database.
⚠️ **THIS IS NOT REQUIRED**
Any settings in the database will override those in the config file, but you may find it less confusing if the setting is only declared in one place.
A non-exhaustive list of settings that are only possible in the config file include the following:
* config :pleroma, Pleroma.Web.Endpoint
* config :pleroma, Pleroma.Repo
* config :pleroma, configurable_from_database
* config :pleroma, :database, rum_enabled
* config :pleroma, :connections_pool
Here is an example of a server config stripped down after migration:
```
use Mix.Config
config :pleroma, Pleroma.Web.Endpoint,
url: [host: "cool.pleroma.site", scheme: "https", port: 443]
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
username: "pleroma",
password: "MySecretPassword",
database: "pleroma_prod",
hostname: "localhost"
config :pleroma, configurable_from_database: true
```
5. Start your instance back up and you can now access the Settings tab in AdminFE.
## Reverting back from database config
1. Stop your Pleroma instance.
2. Run the mix task to migrate back from the database. You'll receive some debugging output and a few messages informing you of what happened.
**Source:**
```
$ mix pleroma.config migrate_from_db
```
or
**OTP:**
```
$ ./bin/pleroma_ctl config migrate_from_db
```
```
10:26:30.593 [debug] QUERY OK source="config" db=9.8ms decode=1.2ms queue=26.0ms idle=0.0ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
10:26:30.659 [debug] QUERY OK source="config" db=1.1ms idle=80.7ms
SELECT c0."id", c0."key", c0."group", c0."value", c0."inserted_at", c0."updated_at" FROM "config" AS c0 []
Database configuration settings have been saved to config/dev.exported_from_db.secret.exs
```
3. The in-database configuration still exists, but it will not be used if you remove `config :pleroma, configurable_from_database: true` from your config.
## Debugging
### Clearing database config
You can clear the database config by truncating the `config` table in the database. e.g.,
```
psql -d pleroma_dev
pleroma_dev=# TRUNCATE config;
TRUNCATE TABLE
```
Additionally, every time you migrate the configuration to the database the config table is automatically truncated to ensure a clean migration.
### Manually removing a setting
If you encounter a situation where the server cannot run properly because of an invalid setting in the database and this is preventing you from accessing AdminFE, you can manually remove the offending setting if you know which one it is.
e.g., here is an example showing a minimal configuration in the database. Only the `config :pleroma, :instance` settings are in the table:
```
psql -d pleroma_dev
pleroma_dev=# select * from config;
id | key | value | inserted_at | updated_at | group
----+-----------+------------------------------------------------------------+---------------------+---------------------+----------
1 | :instance | \x836c0000000168026400046e616d656d00000007426c65726f6d616a | 2020-07-12 15:33:29 | 2020-07-12 15:33:29 | :pleroma
(1 row)
pleroma_dev=# delete from config where key = ':instance' and group = ':pleroma';
DELETE 1
```
Now the `config :pleroma, :instance` settings have been removed from the database.

View file

@ -3,15 +3,48 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Pleroma do
@apps [
:restarter,
:ecto,
:ecto_sql,
:postgrex,
:db_connection,
:cachex,
:flake_id,
:swoosh,
:timex
]
@cachex_children ["object", "user"]
@doc "Common functions to be reused in mix tasks"
def start_pleroma do
Pleroma.Config.Holder.save_default()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do
Application.put_env(:logger, :console, level: :debug)
end
{:ok, _} = Application.ensure_all_started(:pleroma)
apps =
if Application.get_env(:tesla, :adapter) == Tesla.Adapter.Gun do
[:gun | @apps]
else
[:hackney | @apps]
end
Enum.each(apps, &Application.ensure_all_started/1)
children = [
Pleroma.Repo,
{Pleroma.Config.TransferTask, false},
Pleroma.Web.Endpoint
]
cachex_children = Enum.map(@cachex_children, &Pleroma.Application.build_cachex(&1, []))
Supervisor.start_link(children ++ cachex_children,
strategy: :one_for_one,
name: Pleroma.Supervisor
)
if Pleroma.Config.get(:env) not in [:test, :benchmark] do
pleroma_rebooted?()

View file

@ -83,7 +83,7 @@ defp create(group, settings) do
defp migrate_from_db(opts) do
if Pleroma.Config.get([:configurable_from_database]) do
env = opts[:env] || "prod"
env = opts[:env] || Pleroma.Config.get(:env)
config_path =
if Pleroma.Config.get(:release) do
@ -105,6 +105,10 @@ defp migrate_from_db(opts) do
:ok = File.close(file)
System.cmd("mix", ["format", config_path])
shell_info(
"Database configuration settings have been exported to config/#{env}.exported_from_db.secret.exs"
)
else
migration_error()
end
@ -112,7 +116,7 @@ defp migrate_from_db(opts) do
defp migration_error do
shell_error(
"Migration is not allowed in config. You can change this behavior by setting `configurable_from_database` to true."
"Migration is not allowed in config. You can change this behavior by setting `config :pleroma, configurable_from_database: true`"
)
end

View file

@ -145,7 +145,7 @@ def run(["gen" | rest]) do
options,
:uploads_dir,
"What directory should media uploads go in (when using the local uploader)?",
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
Config.get([Pleroma.Uploaders.Local, :uploads])
)
|> Path.expand()
@ -154,7 +154,7 @@ def run(["gen" | rest]) do
options,
:static_dir,
"What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
Pleroma.Config.get([:instance, :static_dir])
Config.get([:instance, :static_dir])
)
|> Path.expand()

View file

@ -232,7 +232,7 @@ def run(["tag", nickname | tags]) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.tag(tags)
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
else
_ ->
shell_error("Could not change user tags for #{nickname}")
@ -245,7 +245,7 @@ def run(["untag", nickname | tags]) do
with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.untag(tags)
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
shell_info("Tags of #{user.nickname}: #{inspect(user.tags)}")
else
_ ->
shell_error("Could not change user tags for #{nickname}")

View file

@ -35,13 +35,14 @@ def user_agent do
# See http://elixir-lang.org/docs/stable/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
Pleroma.Config.Holder.save_default()
Config.Holder.save_default()
Pleroma.HTML.compile_scrubbers()
Config.DeprecationWarnings.warn()
Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
Pleroma.ApplicationRequirements.verify!()
setup_instrumenters()
load_custom_modules()
Pleroma.Docs.JSON.compile()
adapter = Application.get_env(:tesla, :adapter)
@ -162,7 +163,8 @@ defp idempotency_expiration,
defp seconds_valid_interval,
do: :timer.seconds(Config.get!([Pleroma.Captcha, :seconds_valid]))
defp build_cachex(type, opts),
@spec build_cachex(String.t(), keyword()) :: map()
def build_cachex(type, opts),
do: %{
id: String.to_atom("cachex_" <> type),
start: {Cachex, :start_link, [String.to_atom(type <> "_cache"), opts]},

View file

@ -12,6 +12,11 @@ defmodule Pleroma.Config.Loader do
:swarm
]
@reject_groups [
:postgrex,
:tesla
]
if Code.ensure_loaded?(Config.Reader) do
@reader Config.Reader
@ -47,7 +52,8 @@ defp filter(configs) do
@spec filter_group(atom(), keyword()) :: keyword()
def filter_group(group, configs) do
Enum.reject(configs[group], fn {key, _v} ->
key in @reject_keys or (group == :phoenix and key == :serve_endpoints) or group == :postgrex
key in @reject_keys or group in @reject_groups or
(group == :phoenix and key == :serve_endpoints)
end)
end
end

View file

@ -31,8 +31,8 @@ defmodule Pleroma.Config.TransferTask do
{:pleroma, :gopher, [:enabled]}
]
def start_link(_) do
load_and_update_env()
def start_link(restart_pleroma? \\ true) do
load_and_update_env([], restart_pleroma?)
if Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
:ignore
end

View file

@ -6,16 +6,21 @@ def process(implementation, descriptions) do
implementation.process(descriptions)
end
@spec list_modules_in_dir(String.t(), String.t()) :: [module()]
def list_modules_in_dir(dir, start) do
with {:ok, files} <- File.ls(dir) do
files
|> Enum.filter(&String.ends_with?(&1, ".ex"))
|> Enum.map(fn filename ->
module = filename |> String.trim_trailing(".ex") |> Macro.camelize()
String.to_atom(start <> module)
end)
end
@spec list_behaviour_implementations(behaviour :: module()) :: [module()]
def list_behaviour_implementations(behaviour) do
:code.all_loaded()
|> Enum.filter(fn {module, _} ->
# This shouldn't be needed as all modules are expected to have module_info/1,
# but in test enviroments some transient modules `:elixir_compiler_XX`
# are loaded for some reason (where XX is a random integer).
if function_exported?(module, :module_info, 1) do
module.module_info(:attributes)
|> Keyword.get_values(:behaviour)
|> List.flatten()
|> Enum.member?(behaviour)
end
end)
|> Enum.map(fn {module, _} -> module end)
end
@doc """
@ -87,6 +92,12 @@ defp humanize(entity) do
else: string
end
defp format_suggestions({:list_behaviour_implementations, behaviour}) do
behaviour
|> list_behaviour_implementations()
|> format_suggestions()
end
defp format_suggestions([]), do: []
defp format_suggestions([suggestion | tail]) do

View file

@ -1,5 +1,19 @@
defmodule Pleroma.Docs.JSON do
@behaviour Pleroma.Docs.Generator
@external_resource "config/description.exs"
@raw_config Pleroma.Config.Loader.read("config/description.exs")
@raw_descriptions @raw_config[:pleroma][:config_description]
@term __MODULE__.Compiled
@spec compile :: :ok
def compile do
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
end
@spec compiled_descriptions :: Map.t()
def compiled_descriptions do
:persistent_term.get(@term)
end
@spec process(keyword()) :: {:ok, String.t()}
def process(descriptions) do
@ -13,11 +27,4 @@ def process(descriptions) do
{:ok, path}
end
end
def compile do
with config <- Pleroma.Config.Loader.read("config/description.exs") do
config[:pleroma][:config_description]
|> Pleroma.Docs.Generator.convert_to_strings()
end
end
end

View file

@ -68,6 +68,11 @@ defp print_suggestion(file, suggestion, as_list \\ false) do
IO.write(file, " #{list_mark}`#{inspect(suggestion)}`\n")
end
defp print_suggestions(file, {:list_behaviour_implementations, behaviour}) do
suggestions = Pleroma.Docs.Generator.list_behaviour_implementations(behaviour)
print_suggestions(file, suggestions)
end
defp print_suggestions(_file, nil), do: nil
defp print_suggestions(_file, ""), do: nil

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Emails.AdminEmail do
alias Pleroma.Config
alias Pleroma.Web.Router.Helpers
defp instance_config, do: Pleroma.Config.get(:instance)
defp instance_config, do: Config.get(:instance)
defp instance_name, do: instance_config()[:name]
defp instance_notify_email do
@ -72,6 +72,8 @@ def report(to, reporter, account, statuses, comment) do
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
#{comment_html}
#{statuses_html}
<p>
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>
"""
new()

View file

@ -108,7 +108,7 @@ defp load_pack(pack_dir, emoji_groups) do
if File.exists?(emoji_txt) do
load_from_file(emoji_txt, emoji_groups)
else
extensions = Pleroma.Config.get([:emoji, :pack_extensions])
extensions = Config.get([:emoji, :pack_extensions])
Logger.info(
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{

View file

@ -34,10 +34,18 @@ def get(id, %{id: user_id} = _user) do
Repo.one(query)
end
def get_filters(%User{id: user_id} = _user) do
def get_active(query) do
from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
end
def get_irreversible(query) do
from(f in query, where: f.hide)
end
def get_filters(query \\ __MODULE__, %User{id: user_id}) do
query =
from(
f in Pleroma.Filter,
f in query,
where: f.user_id == ^user_id,
order_by: [desc: :id]
)
@ -95,4 +103,34 @@ def update(%Pleroma.Filter{} = filter, params) do
|> validate_required([:phrase, :context])
|> Repo.update()
end
def compose_regex(user_or_filters, format \\ :postgres)
def compose_regex(%User{} = user, format) do
__MODULE__
|> get_active()
|> get_irreversible()
|> get_filters(user)
|> compose_regex(format)
end
def compose_regex([_ | _] = filters, format) do
phrases =
filters
|> Enum.map(& &1.phrase)
|> Enum.join("|")
case format do
:postgres ->
"\\y(#{phrases})\\y"
:re ->
~r/\b#{phrases}\b/i
_ ->
nil
end
end
def compose_regex(_, _), do: nil
end

View file

@ -109,7 +109,7 @@ def extract_first_external_url(object, content) do
result =
content
|> Floki.parse_fragment!()
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|> Floki.filter_out("a.mention,a.hashtag,a.attachment,a[rel~=\"tag\"]")
|> Floki.attribute("a", "href")
|> Enum.at(0)

View file

@ -17,6 +17,8 @@ defmodule Pleroma.Instances.Instance do
schema "instances" do
field(:host, :string)
field(:unreachable_since, :naive_datetime_usec)
field(:favicon, :string)
field(:favicon_updated_at, :naive_datetime)
timestamps()
end
@ -25,7 +27,7 @@ defmodule Pleroma.Instances.Instance do
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:host, :unreachable_since])
|> cast(params, [:host, :unreachable_since, :favicon, :favicon_updated_at])
|> validate_required([:host])
|> unique_constraint(:host)
end
@ -120,4 +122,48 @@ defp parse_datetime(datetime) when is_binary(datetime) do
end
defp parse_datetime(datetime), do: datetime
def get_or_update_favicon(%URI{host: host} = instance_uri) do
existing_record = Repo.get_by(Instance, %{host: host})
now = NaiveDateTime.utc_now()
if existing_record && existing_record.favicon_updated_at &&
NaiveDateTime.diff(now, existing_record.favicon_updated_at) < 86_400 do
existing_record.favicon
else
favicon = scrape_favicon(instance_uri)
if existing_record do
existing_record
|> changeset(%{favicon: favicon, favicon_updated_at: now})
|> Repo.update()
else
%Instance{}
|> changeset(%{host: host, favicon: favicon, favicon_updated_at: now})
|> Repo.insert()
end
favicon
end
end
defp scrape_favicon(%URI{} = instance_uri) do
try do
with {:ok, %Tesla.Env{body: html}} <-
Pleroma.HTTP.get(to_string(instance_uri), [{:Accept, "text/html"}]),
favicon_rel <-
html
|> Floki.parse_document!()
|> Floki.attribute("link[rel=icon]", "href")
|> List.first(),
favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(),
true <- is_binary(favicon) do
favicon
else
_ -> nil
end
rescue
_ -> nil
end
end
end

View file

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MigrationHelper.NotificationBackfill do
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
@ -25,18 +24,27 @@ def fill_in_notification_types do
|> type_from_activity()
notification
|> Notification.changeset(%{type: type})
|> Ecto.Changeset.change(%{type: type})
|> Repo.update()
end)
end
defp get_by_ap_id(ap_id) do
q =
from(u in User,
select: u.id
)
Repo.get_by(q, ap_id: ap_id)
end
# This is copied over from Notifications to keep this stable.
defp type_from_activity(%{data: %{"type" => type}} = activity) do
case type do
"Follow" ->
accepted_function = fn activity ->
with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
%User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
with %User{} = follower <- get_by_ap_id(activity.data["actor"]),
%User{} = followed <- get_by_ap_id(activity.data["object"]) do
Pleroma.FollowingRelationship.following?(follower, followed)
end
end

View file

@ -130,6 +130,7 @@ def for_user_query(user, opts \\ %{}) do
|> preload([n, a, o], activity: {a, object: o})
|> exclude_notification_muted(user, exclude_notification_muted_opts)
|> exclude_blocked(user, exclude_blocked_opts)
|> exclude_filtered(user)
|> exclude_visibility(opts)
end
@ -158,6 +159,20 @@ defp exclude_notification_muted(query, user, opts) do
|> where([n, a, o, tm], is_nil(tm.user_id))
end
defp exclude_filtered(query, user) do
case Pleroma.Filter.compose_regex(user) do
nil ->
query
regex ->
from([_n, a, o] in query,
where:
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
)
end
end
@valid_visibilities ~w[direct unlisted public private]
defp exclude_visibility(query, %{exclude_visibilities: visibility})
@ -337,6 +352,7 @@ def dismiss(%{id: user_id} = _user, id) do
end
end
@spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []}
def create_notifications(activity, options \\ [])
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do
@ -367,6 +383,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
do_send = do_send && user in enabled_receivers
create_notification(activity, user, do_send)
end)
|> Enum.reject(&is_nil/1)
{:ok, notifications}
end
@ -480,6 +497,10 @@ def get_potential_receiver_ap_ids(%{data: %{"type" => type, "object" => object_i
end
end
def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do
[object_id]
end
def get_potential_receiver_ap_ids(activity) do
[]
|> Utils.maybe_notify_to_recipients(activity)
@ -551,7 +572,8 @@ def skip?(%Activity{} = activity, %User{} = user) do
:self,
:invisible,
:block_from_strangers,
:recently_followed
:recently_followed,
:filtered
]
|> Enum.find(&skip?(&1, activity, user))
end
@ -590,6 +612,26 @@ def skip?(:recently_followed, %Activity{data: %{"type" => "Follow"}} = activity,
end)
end
def skip?(:filtered, %{data: %{"type" => type}}, _) when type in ["Follow", "Move"], do: false
def skip?(:filtered, activity, user) do
object = Object.normalize(activity)
cond do
is_nil(object) ->
false
object.data["actor"] == user.ap_id ->
false
not is_nil(regex = Pleroma.Filter.compose_regex(user, :re)) ->
Regex.match?(regex, object.data["content"])
true ->
false
end
end
def skip?(_, _, _), do: false
def for_user_and_activity(user, activity) do

View file

@ -83,8 +83,8 @@ def fetch_object_from_id(id, options \\ []) do
{:transmogrifier, {:error, {:reject, nil}}} ->
{:reject, nil}
{:transmogrifier, _} ->
{:error, "Transmogrifier failure."}
{:transmogrifier, _} = e ->
{:error, e}
{:object, data, nil} ->
reinject_object(%Object{}, data)

View file

@ -69,10 +69,11 @@ defp csp_string do
img_src = "img-src 'self' data: blob:"
media_src = "media-src 'self'"
# Strict multimedia CSP enforcement only when MediaProxy is enabled
{img_src, media_src} =
if Config.get([:media_proxy, :enabled]) &&
!Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
sources = get_proxy_and_attachment_sources()
sources = build_csp_multimedia_source_list()
{[img_src, sources], [media_src, sources]}
else
{[img_src, " https:"], [media_src, " https:"]}
@ -81,14 +82,14 @@ defp csp_string do
connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
connect_src =
if Pleroma.Config.get(:env) == :dev do
if Config.get(:env) == :dev do
[connect_src, " http://localhost:3035/"]
else
connect_src
end
script_src =
if Pleroma.Config.get(:env) == :dev do
if Config.get(:env) == :dev do
"script-src 'self' 'unsafe-eval'"
else
"script-src 'self'"
@ -107,29 +108,28 @@ defp csp_string do
|> :erlang.iolist_to_binary()
end
defp get_proxy_and_attachment_sources do
defp build_csp_multimedia_source_list do
media_proxy_whitelist =
Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->
add_source(acc, host)
end)
media_proxy_base_url =
if Config.get([:media_proxy, :base_url]),
do: URI.parse(Config.get([:media_proxy, :base_url])).host
media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url]))
upload_base_url =
if Config.get([Pleroma.Upload, :base_url]),
do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host
upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url]))
s3_endpoint =
if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3,
do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host
s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint]))
captcha_method = Config.get([Pleroma.Captcha, :method])
captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint]))
[]
|> add_source(media_proxy_base_url)
|> add_source(upload_base_url)
|> add_source(s3_endpoint)
|> add_source(media_proxy_whitelist)
|> add_source(captcha_endpoint)
end
defp add_source(iodata, nil), do: iodata
@ -139,6 +139,16 @@ defp add_csp_param(csp_iodata, nil), do: csp_iodata
defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
defp build_csp_param(nil), do: nil
defp build_csp_param(url) when is_binary(url) do
%{host: host, scheme: scheme} = URI.parse(url)
if scheme do
[scheme, "://", host]
end
end
def warn_if_disabled do
unless Config.get([:http_security, :enabled]) do
Logger.warn("

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Plugs.StaticFEPlug do
def init(options), do: options
def call(conn, _) do
if enabled?() and accepts_html?(conn) do
if enabled?() and requires_html?(conn) do
conn
|> StaticFEController.call(:show)
|> halt()
@ -20,10 +20,7 @@ def call(conn, _) do
defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
defp accepts_html?(conn) do
case get_req_header(conn, "accept") do
[accept | _] -> String.contains?(accept, "text/html")
_ -> false
end
defp requires_html?(conn) do
Phoenix.Controller.get_format(conn) == "html"
end
end

View file

@ -3,12 +3,13 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy do
@range_headers ~w(range if-range)
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match if-range range)
~w(if-unmodified-since if-none-match) ++ @range_headers
@resp_cache_headers ~w(etag date last-modified)
@keep_resp_headers @resp_cache_headers ++
~w(content-type content-disposition content-encoding content-range) ++
~w(accept-ranges vary)
~w(content-length content-type content-disposition content-encoding) ++
~w(content-range accept-ranges vary)
@default_cache_control_header "public, max-age=1209600"
@valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30)
@ -170,6 +171,8 @@ defp request(method, url, headers, opts) do
end
defp response(conn, client, url, status, headers, opts) do
Logger.debug("#{__MODULE__} #{status} #{url} #{inspect(headers)}")
result =
conn
|> put_resp_headers(build_resp_headers(headers, opts))
@ -220,7 +223,9 @@ defp chunk_reply(conn, client, opts, sent_so_far, duration) do
end
end
defp head_response(conn, _url, code, headers, opts) do
defp head_response(conn, url, code, headers, opts) do
Logger.debug("#{__MODULE__} #{code} #{url} #{inspect(headers)}")
conn
|> put_resp_headers(build_resp_headers(headers, opts))
|> send_resp(code, "")
@ -262,20 +267,33 @@ defp build_req_headers(headers, opts) do
headers
|> downcase_headers()
|> Enum.filter(fn {k, _} -> k in @keep_req_headers end)
|> (fn headers ->
headers = headers ++ Keyword.get(opts, :req_headers, [])
|> build_req_range_or_encoding_header(opts)
|> build_req_user_agent_header(opts)
|> Keyword.merge(Keyword.get(opts, :req_headers, []))
end
if Keyword.get(opts, :keep_user_agent, false) do
List.keystore(
headers,
"user-agent",
0,
{"user-agent", Pleroma.Application.user_agent()}
)
else
headers
end
end).()
# Disable content-encoding if any @range_headers are requested (see #1823).
defp build_req_range_or_encoding_header(headers, _opts) do
range? = Enum.any?(headers, fn {header, _} -> Enum.member?(@range_headers, header) end)
if range? && List.keymember?(headers, "accept-encoding", 0) do
List.keydelete(headers, "accept-encoding", 0)
else
headers
end
end
defp build_req_user_agent_header(headers, opts) do
if Keyword.get(opts, :keep_user_agent, false) do
List.keystore(
headers,
"user-agent",
0,
{"user-agent", Pleroma.Application.user_agent()}
)
else
headers
end
end
defp build_resp_headers(headers, opts) do
@ -283,7 +301,7 @@ defp build_resp_headers(headers, opts) do
|> Enum.filter(fn {k, _} -> k in @keep_resp_headers end)
|> build_resp_cache_headers(opts)
|> build_resp_content_disposition_header(opts)
|> (fn headers -> headers ++ Keyword.get(opts, :resp_headers, []) end).()
|> Keyword.merge(Keyword.get(opts, :resp_headers, []))
end
defp build_resp_cache_headers(headers, _opts) do

View file

@ -63,6 +63,10 @@ def store(upload, opts \\ []) do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
description = Map.get(opts, :description) || upload.name,
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
{:ok, url_spec} <- Pleroma.Uploaders.Uploader.put_file(opts.uploader, upload) do
{:ok,
%{
@ -75,9 +79,12 @@ def store(upload, opts \\ []) do
"href" => url_from_spec(upload, opts.base_url, url_spec)
}
],
"name" => Map.get(opts, :description) || upload.name
"name" => description
}}
else
{:description_limit, _} ->
{:error, :description_too_long}
{:error, error} ->
Logger.error(
"#{__MODULE__} store (using #{inspect(opts.uploader)}) failed: #{inspect(error)}"

View file

@ -0,0 +1,18 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.Exiftool do
@moduledoc """
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
"""
@behaviour Pleroma.Upload.Filter
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
System.cmd("exiftool", ["-overwrite_original", "-gps:all=", file], parallelism: true)
:ok
end
def filter(_), do: :ok
end

View file

@ -89,7 +89,7 @@ defmodule Pleroma.User do
field(:keys, :string)
field(:public_key, :string)
field(:ap_id, :string)
field(:avatar, :map)
field(:avatar, :map, default: %{})
field(:local, :boolean, default: true)
field(:follower_address, :string)
field(:following_address, :string)
@ -115,7 +115,7 @@ defmodule Pleroma.User do
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
field(:settings, :map, default: nil)
field(:mastofe_settings, :map, default: nil)
field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_count, :boolean, default: false)
field(:hide_follows_count, :boolean, default: false)
@ -138,6 +138,7 @@ defmodule Pleroma.User do
field(:also_known_as, {:array, :string}, default: [])
field(:inbox, :string)
field(:shared_inbox, :string)
field(:accepts_chat_messages, :boolean, default: nil)
embeds_one(
:notification_settings,
@ -388,8 +389,8 @@ defp fix_follower_address(%{nickname: nickname} = params),
defp fix_follower_address(params), do: params
def remote_user_changeset(struct \\ %User{local: false}, params) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
name =
case params[:name] do
@ -436,7 +437,8 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
:discoverable,
:invisible,
:actor_type,
:also_known_as
:also_known_as,
:accepts_chat_messages
]
)
|> validate_required([:name, :ap_id])
@ -448,8 +450,8 @@ def remote_user_changeset(struct \\ %User{local: false}, 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)
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
struct
|> cast(
@ -481,7 +483,8 @@ def update_changeset(struct, params \\ %{}) do
:pleroma_settings_store,
:discoverable,
:actor_type,
:also_known_as
:also_known_as,
:accepts_chat_messages
]
)
|> unique_constraint(:nickname)
@ -527,11 +530,21 @@ defp parse_fields(value) do
end
defp put_emoji(changeset) do
bio = get_change(changeset, :bio)
name = get_change(changeset, :name)
emojified_fields = [:bio, :name, :raw_fields]
if Enum.any?(changeset.changes, fn {k, _} -> k in emojified_fields end) do
bio = Emoji.Formatter.get_emoji_map(get_field(changeset, :bio))
name = Emoji.Formatter.get_emoji_map(get_field(changeset, :name))
emoji = Map.merge(bio, name)
emoji =
changeset
|> get_field(:raw_fields)
|> Enum.reduce(emoji, fn x, acc ->
Map.merge(acc, Emoji.Formatter.get_emoji_map(x["name"] <> x["value"]))
end)
if bio || name do
emoji = Map.merge(Emoji.Formatter.get_emoji_map(bio), Emoji.Formatter.get_emoji_map(name))
put_change(changeset, :emoji, emoji)
else
changeset
@ -539,14 +552,11 @@ defp put_emoji(changeset) do
end
defp put_change_if_present(changeset, map_field, value_function) do
if value = get_change(changeset, map_field) do
with {:ok, new_value} <- value_function.(value) do
put_change(changeset, map_field, new_value)
else
_ -> changeset
end
with {:ok, value} <- fetch_change(changeset, map_field),
{:ok, new_value} <- value_function.(value) do
put_change(changeset, map_field, new_value)
else
changeset
_ -> changeset
end
end
@ -621,12 +631,13 @@ def force_password_reset_async(user) do
def force_password_reset(user), do: update_password_reset_pending(user, true)
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)
bio_limit = Config.get([:instance, :user_bio_length], 5000)
name_limit = Config.get([:instance, :user_name_length], 100)
params = Map.put_new(params, :accepts_chat_messages, true)
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
Pleroma.Config.get([:instance, :account_activation_required])
Config.get([:instance, :account_activation_required])
else
opts[:need_confirmation]
end
@ -641,13 +652,14 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
:nickname,
:password,
:password_confirmation,
:emoji
:emoji,
:accepts_chat_messages
])
|> validate_required([:name, :nickname, :password, :password_confirmation])
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> unique_constraint(:nickname)
|> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: bio_limit)
@ -662,7 +674,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
def maybe_validate_required_email(changeset, true), do: changeset
def maybe_validate_required_email(changeset, _) do
if Pleroma.Config.get([:instance, :account_activation_required]) do
if Config.get([:instance, :account_activation_required]) do
validate_required(changeset, [:email])
else
changeset
@ -682,7 +694,7 @@ defp put_following_and_follower_address(changeset) do
end
defp autofollow_users(user) do
candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
candidates = Config.get([:instance, :autofollowed_nicknames])
autofollowed_users =
User.Query.build(%{nickname: candidates, local: true, deactivated: false})
@ -709,7 +721,7 @@ def post_register_action(%User{} = user) do
def try_send_confirmation_email(%User{} = user) do
if user.confirmation_pending &&
Pleroma.Config.get([:instance, :account_activation_required]) do
Config.get([:instance, :account_activation_required]) do
user
|> Pleroma.Emails.UserEmail.account_confirmation_email()
|> Pleroma.Emails.Mailer.deliver_async()
@ -766,7 +778,7 @@ def follow_all(follower, followeds) do
defdelegate following(user), to: FollowingRelationship
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
cond do
followed.deactivated ->
@ -967,7 +979,7 @@ def get_cached_by_nickname(nickname) do
end
def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do
restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content])
restrict_to_local = Config.get([:instance, :limit_to_local_content])
cond do
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
@ -1163,7 +1175,7 @@ defp follow_information_changeset(user, params) do
@spec update_follower_count(User.t()) :: {:ok, User.t()}
def update_follower_count(%User{} = user) do
if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do
if user.local or !Config.get([:instance, :external_user_synchronization]) do
follower_count = FollowingRelationship.follower_count(user)
user
@ -1176,7 +1188,7 @@ def update_follower_count(%User{} = user) do
@spec update_following_count(User.t()) :: {:ok, User.t()}
def update_following_count(%User{local: false} = user) do
if Pleroma.Config.get([:instance, :external_user_synchronization]) do
if Config.get([:instance, :external_user_synchronization]) do
{:ok, maybe_fetch_follow_information(user)}
else
{:ok, user}
@ -1263,7 +1275,7 @@ def unmute(%User{} = muter, %User{} = mutee) do
end
def subscribe(%User{} = subscriber, %User{} = target) do
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
deny_follow_blocked = Config.get([:user, :deny_follow_blocked])
if blocks?(target, subscriber) and deny_follow_blocked do
{:error, "Could not subscribe: #{target.nickname} is blocking you"}
@ -1309,7 +1321,8 @@ def block(%User{} = blocker, %User{} = blocked) do
unsubscribe(blocked, blocker)
if following?(blocked, blocker), do: unfollow(blocked, blocker)
unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
{:ok, blocker} = update_follower_count(blocker)
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
@ -1527,8 +1540,7 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
blocked_identifiers,
fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
{:ok, _user_block} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do
{:ok, _block} <- CommonAPI.block(blocker, blocked) do
blocked
else
err ->
@ -1546,7 +1558,7 @@ def perform(:follow_import, %User{} = follower, followed_identifiers)
fn followed_identifier ->
with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),
{:ok, follower} <- maybe_direct_follow(follower, followed),
{:ok, _} <- ActivityPub.follow(follower, followed) do
{:ok, _, _, _} <- CommonAPI.follow(follower, followed) do
followed
else
err ->
@ -1654,7 +1666,7 @@ def html_filter_policy(%User{no_rich_text: true}) do
Pleroma.HTML.Scrubber.TwitterText
end
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])
def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id)
@ -1836,7 +1848,7 @@ defp normalize_tags(tags) do
end
defp local_nickname_regex do
if Pleroma.Config.get([:instance, :extended_nickname_format]) do
if Config.get([:instance, :extended_nickname_format]) do
@extended_local_nickname_regex
else
@strict_local_nickname_regex
@ -1964,8 +1976,8 @@ def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
def get_mascot(%{mascot: mascot}) when is_nil(mascot) do
# use instance-default
config = Pleroma.Config.get([:assets, :mascots])
default_mascot = Pleroma.Config.get([:assets, :default_mascot])
config = Config.get([:assets, :mascots])
default_mascot = Config.get([:assets, :default_mascot])
mascot = Keyword.get(config, default_mascot)
%{
@ -2060,7 +2072,7 @@ def roles(%{is_moderator: is_moderator, is_admin: is_admin}) do
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)
limit = Config.get([:instance, limit_name], 0)
changeset
|> validate_length(:fields, max: limit)
@ -2074,8 +2086,8 @@ def validate_fields(changeset, remote? \\ false) do
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)
name_limit = Config.get([:instance, :account_field_name_length], 255)
value_limit = Config.get([:instance, :account_field_value_length], 255)
is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&
String.length(value) <= value_limit
@ -2085,10 +2097,10 @@ defp valid_field?(_), do: false
defp truncate_field(%{"name" => name, "value" => value}) do
{name, _chopped} =
String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255))
String.split_at(name, Config.get([:instance, :account_field_name_length], 255))
{value, _chopped} =
String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255))
String.split_at(value, Config.get([:instance, :account_field_value_length], 255))
%{"name" => name, "value" => value}
end
@ -2118,8 +2130,8 @@ def mascot_update(user, url) do
def mastodon_settings_update(user, settings) do
user
|> cast(%{settings: settings}, [:settings])
|> validate_required([:settings])
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|> validate_required([:mastofe_settings])
|> update_and_set_cache()
end
@ -2143,7 +2155,7 @@ def confirmation_changeset(user, need_confirmation: need_confirmation?) do
def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do
if id not in user.pinned_activities do
max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0)
max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)
params = %{pinned_activities: user.pinned_activities ++ [id]}
user

View file

@ -52,6 +52,7 @@ defp search_query(query_string, for_user, following) do
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_invisible_users()
|> filter_internal_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
|> trigram_rank(query_string)
@ -68,11 +69,15 @@ defp fts_search(query, query_string) do
u in query,
where:
fragment(
# The fragment must _exactly_ match `users_fts_index`, otherwise the index won't work
"""
(to_tsvector('simple', ?) || to_tsvector('simple', ?)) @@ to_tsquery('simple', ?)
(
setweight(to_tsvector('simple', regexp_replace(?, '\\W', ' ', 'g')), 'A') ||
setweight(to_tsvector('simple', regexp_replace(coalesce(?, ''), '\\W', ' ', 'g')), 'B')
) @@ to_tsquery('simple', ?)
""",
u.name,
u.nickname,
u.name,
^query_string
)
)
@ -87,15 +92,23 @@ defp to_tsquery(query_string) do
|> Enum.join(" | ")
end
# Considers nickname match, localized nickname match, name match; preferences nickname match
defp trigram_rank(query, query_string) do
from(
u in query,
select_merge: %{
search_rank:
fragment(
"similarity(?, trim(? || ' ' || coalesce(?, '')))",
"""
similarity(?, ?) +
similarity(?, regexp_replace(?, '@.+', '')) +
similarity(?, trim(coalesce(?, '')))
""",
^query_string,
u.nickname,
^query_string,
u.nickname,
^query_string,
u.name
)
}
@ -109,6 +122,10 @@ defp filter_invisible_users(query) do
from(q in query, where: q.invisible == false)
end
defp filter_internal_users(query) do
from(q in query, where: q.actor_type != "Application")
end
defp filter_blocked_user(query, %User{} = blocker) do
query
|> join(:left, [u], b in Pleroma.UserRelationship,

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Constants
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.Filter
alias Pleroma.Maps
alias Pleroma.Notification
alias Pleroma.Object
@ -321,28 +322,6 @@ defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
end
end
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
{:ok, Activity.t()} | {:error, any()}
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
with {:ok, result} <-
Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do
result
end
end
defp do_follow(follower, followed, activity_id, local, opts) do
skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false)
data = make_follow_data(follower, followed, activity_id)
with {:ok, activity} <- insert(data, local),
_ <- skip_notify_and_stream || notify_and_stream(activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
{:error, error} -> Repo.rollback(error)
end
end
@spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | nil | {:error, any()}
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
@ -366,33 +345,6 @@ defp do_unfollow(follower, followed, activity_id, local) do
end
end
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | {:error, any()}
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
with {:ok, result} <-
Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do
result
end
end
defp do_block(blocker, blocked, activity_id, local) do
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
if unfollow_blocked and fetch_latest_follow(blocker, blocked) do
unfollow(blocker, blocked, nil, local)
end
block_data = make_block_data(blocker, blocked, activity_id)
with {:ok, activity} <- insert(block_data, local),
_ <- notify_and_stream(activity),
:ok <- maybe_federate(activity) do
{:ok, activity}
else
{:error, error} -> Repo.rollback(error)
end
end
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
def flag(
%{
@ -473,6 +425,7 @@ def fetch_activities_for_context_query(context, opts) do
|> maybe_set_thread_muted_field(opts)
|> restrict_blocked(opts)
|> restrict_recipients(recipients, opts[:user])
|> restrict_filtered(opts)
|> where(
[activity],
fragment(
@ -988,6 +941,26 @@ defp restrict_instance(query, %{instance: instance}) do
defp restrict_instance(query, _), do: query
defp restrict_filtered(query, %{user: %User{} = user}) do
case Filter.compose_regex(user) do
nil ->
query
regex ->
from([activity, object] in query,
where:
fragment("not(?->>'content' ~* ?)", object.data, ^regex) or
activity.actor == ^user.ap_id
)
end
end
defp restrict_filtered(query, %{blocking_user: %User{} = user}) do
restrict_filtered(query, %{user: user})
end
defp restrict_filtered(query, _), do: query
defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query
defp exclude_poll_votes(query, _) do
@ -1118,6 +1091,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_favorited_by(opts)
|> restrict_blocked(restrict_blocked_opts)
|> restrict_muted(restrict_muted_opts)
|> restrict_filtered(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
|> restrict_thread_visibility(opts, config)
@ -1126,6 +1100,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts)
|> restrict_announce_object_actor(opts)
|> restrict_filtered(opts)
|> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts)
|> exclude_chat_messages(opts)
@ -1251,6 +1226,8 @@ defp object_to_user_data(data) do
end)
locked = data["manuallyApprovesFollowers"] || false
capabilities = data["capabilities"] || %{}
accepts_chat_messages = capabilities["acceptsChatMessages"]
data = Transmogrifier.maybe_fix_user_object(data)
discoverable = data["discoverable"] || false
invisible = data["invisible"] || false
@ -1289,7 +1266,8 @@ defp object_to_user_data(data) do
also_known_as: Map.get(data, "alsoKnownAs", []),
public_key: public_key,
inbox: data["inbox"],
shared_inbox: shared_inbox
shared_inbox: shared_inbox,
accepts_chat_messages: accepts_chat_messages
}
# nickname can be nil because of virtual actors
@ -1398,6 +1376,31 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
end
end
def maybe_handle_clashing_nickname(data) do
nickname = data[:nickname]
with %User{} = old_user <- User.get_by_nickname(nickname),
{_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
Logger.info(
"Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
data[:ap_id]
}, renaming."
)
old_user
|> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"})
|> User.update_and_set_cache()
else
{:ap_id_comparison, true} ->
Logger.info(
"Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
)
_ ->
nil
end
end
def make_user_from_ap_id(ap_id) do
user = User.get_cached_by_ap_id(ap_id)
@ -1410,6 +1413,8 @@ def make_user_from_ap_id(ap_id) do
|> User.remote_user_changeset(data)
|> User.update_and_set_cache()
else
maybe_handle_clashing_nickname(data)
data
|> User.remote_user_changeset()
|> Repo.insert()

View file

@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do
require Pleroma.Constants
@spec follow(User.t(), User.t()) :: {:ok, map(), keyword()}
def follow(follower, followed) do
data = %{
"id" => Utils.generate_activity_id(),
"actor" => follower.ap_id,
"type" => "Follow",
"object" => followed.ap_id,
"to" => [followed.ap_id]
}
{:ok, data, []}
end
@spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}
def emoji_react(actor, object, emoji) do
with {:ok, data, meta} <- object_action(actor, object) do
@ -138,6 +151,18 @@ def update(actor, object) do
}, []}
end
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
def block(blocker, blocked) do
{:ok,
%{
"id" => Utils.generate_activity_id(),
"type" => "Block",
"actor" => blocker.ap_id,
"object" => blocked.ap_id,
"to" => [blocked.ap_id]
}, []}
end
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
def announce(actor, object, options \\ []) do
public? = Keyword.get(options, :public, false)

View file

@ -27,11 +27,14 @@ defp contains_links?(_), do: false
@impl true
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
{:contains_links, true} <- {:contains_links, contains_links?(object)},
{:old_user, true} <- {:old_user, old_user?(u)} do
{:ok, message}
else
{:ok, %User{local: true}} ->
{:ok, message}
{:contains_links, false} ->
{:ok, message}

View file

@ -98,7 +98,7 @@ def filter(message), do: {:ok, message}
@impl true
def describe do
mrf_object_age =
Pleroma.Config.get(:mrf_object_age)
Config.get(:mrf_object_age)
|> Enum.into(%{})
{:ok, %{mrf_object_age: mrf_object_age}}

View file

@ -47,5 +47,5 @@ def filter(object), do: {:ok, object}
@impl true
def describe,
do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
end

View file

@ -155,7 +155,7 @@ def filter(%{"type" => "Delete", "actor" => actor} = object) do
%{host: actor_host} = URI.parse(actor)
reject_deletes =
Pleroma.Config.get([:mrf_simple, :reject_deletes])
Config.get([:mrf_simple, :reject_deletes])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(reject_deletes, actor_host) do

View file

@ -13,10 +13,12 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@ -24,6 +26,35 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
def validate(object, meta)
def validate(%{"type" => "Follow"} = object, meta) do
with {:ok, object} <-
object
|> FollowValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
end
end
def validate(%{"type" => "Block"} = block_activity, meta) do
with {:ok, block_activity} <-
block_activity
|> BlockValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
block_activity = stringify_keys(block_activity)
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
meta =
if !outgoing_blocks do
Keyword.put(meta, :do_not_federate, true)
else
meta
end
{:ok, block_activity, meta}
end
end
def validate(%{"type" => "Update"} = update_activity, meta) do
with {:ok, update_activity} <-
update_activity

View file

@ -0,0 +1,42 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
end
def cast_data(data) do
%__MODULE__{}
|> cast(data, __schema__(:fields))
end
def validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Block"])
|> validate_actor_presence()
|> validate_actor_presence(field_name: :object)
end
def cast_and_validate(data) do
data
|> cast_data
|> validate_data
end
end

View file

@ -93,12 +93,14 @@ def validate_content_or_attachment(cng) do
- If both users are in our system
- If at least one of the users in this ChatMessage is a local user
- If the recipient is not blocking the actor
- If the recipient is explicitly not accepting chat messages
"""
def validate_local_concern(cng) do
with actor_ap <- get_field(cng, :actor),
{_, %User{} = actor} <- {:find_actor, User.get_cached_by_ap_id(actor_ap)},
{_, %User{} = recipient} <-
{:find_recipient, User.get_cached_by_ap_id(get_field(cng, :to) |> hd())},
{_, false} <- {:not_accepting_chats?, recipient.accepts_chat_messages == false},
{_, false} <- {:blocking_actor?, User.blocks?(recipient, actor)},
{_, true} <- {:local?, Enum.any?([actor, recipient], & &1.local)} do
cng
@ -107,6 +109,10 @@ def validate_local_concern(cng) do
cng
|> add_error(:actor, "actor is blocked by recipient")
{:not_accepting_chats?, true} ->
cng
|> add_error(:to, "recipient does not accept chat messages")
{:local?, false} ->
cng
|> add_error(:actor, "actor and recipient are both remote")

View file

@ -0,0 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
import Ecto.Changeset
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:type, :string)
field(:actor, ObjectValidators.ObjectID)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID)
field(:state, :string, default: "pending")
end
def cast_data(data) do
%__MODULE__{}
|> cast(data, __schema__(:fields))
end
def validate_data(cng) do
cng
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Follow"])
|> validate_inclusion(:state, ~w{pending reject accept})
|> validate_actor_presence()
|> validate_actor_presence(field_name: :object)
end
def cast_and_validate(data) do
data
|> cast_data
|> validate_data
end
end

View file

@ -28,7 +28,7 @@ def relay_ap_id do
def follow(target_instance) do
with %User{} = local_user <- get_actor(),
{:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
{:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity}
else

View file

@ -6,8 +6,10 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
collection, and so on.
"""
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.FollowingRelationship
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
@ -20,6 +22,84 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
def handle(object, meta \\ [])
# Tasks this handle
# - Follows if possible
# - Sends a notification
# - Generates accept or reject if appropriate
def handle(
%{
data: %{
"id" => follow_id,
"type" => "Follow",
"object" => followed_user,
"actor" => following_user
}
} = object,
meta
) do
with %User{} = follower <- User.get_cached_by_ap_id(following_user),
%User{} = followed <- User.get_cached_by_ap_id(followed_user),
{_, {:ok, _}, _, _} <-
{:following, User.follow(follower, followed, :follow_pending), follower, followed} do
if followed.local && !followed.locked do
Utils.update_follow_state_for_all(object, "accept")
FollowingRelationship.update(follower, followed, :follow_accept)
User.update_follower_count(followed)
User.update_following_count(follower)
%{
to: [following_user],
actor: followed,
object: follow_id,
local: true
}
|> ActivityPub.accept()
end
else
{:following, {:error, _}, follower, followed} ->
Utils.update_follow_state_for_all(object, "reject")
FollowingRelationship.update(follower, followed, :follow_reject)
if followed.local do
%{
to: [follower.ap_id],
actor: followed,
object: follow_id,
local: true
}
|> ActivityPub.reject()
end
_ ->
nil
end
{:ok, notifications} = Notification.create_notifications(object, do_send: false)
meta =
meta
|> add_notifications(notifications)
updated_object = Activity.get_by_ap_id(follow_id)
{:ok, updated_object, meta}
end
# Tasks this handles:
# - Unfollow and block
def handle(
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
object,
meta
) do
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
User.block(blocker, blocked)
end
{:ok, object, meta}
end
# Tasks this handles:
# - Update the user
#
@ -82,7 +162,10 @@ def handle(%{data: %{"type" => "Announce"}} = object, meta) do
if !User.is_internal_user?(user) do
Notification.create_notifications(object)
ActivityPub.stream_out(object)
object
|> Topics.get_activity_topics()
|> Streamer.stream(object)
end
{:ok, object, meta}
@ -190,14 +273,20 @@ def handle_object_creation(object) do
{:ok, object}
end
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_like_from_object(object, liked_object),
{:ok, _} <- Repo.delete(object) do
:ok
defp undo_like(nil, object), do: delete_object(object)
defp undo_like(%Object{} = liked_object, object) do
with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do
delete_object(object)
end
end
def handle_undoing(%{data: %{"type" => "Like"}} = object) do
object.data["object"]
|> Object.get_by_ap_id()
|> undo_like(object)
end
def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do
with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),
{:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object),
@ -227,6 +316,11 @@ def handle_undoing(
def handle_undoing(object), do: {:error, ["don't know how to handle", object]}
@spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()}
defp delete_object(object) do
with {:ok, _} <- Repo.delete(object), do: :ok
end
defp send_notifications(meta) do
Keyword.get(meta, :notifications, [])
|> Enum.each(fn notification ->

View file

@ -233,18 +233,24 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
is_map(url) && is_binary(url["href"]) -> url["href"]
is_binary(data["url"]) -> data["url"]
is_binary(data["href"]) -> data["href"]
true -> nil
end
attachment_url =
%{"href" => href}
|> Maps.put_if_present("mediaType", media_type)
|> Maps.put_if_present("type", Map.get(url || %{}, "type"))
if href do
attachment_url =
%{"href" => href}
|> Maps.put_if_present("mediaType", media_type)
|> Maps.put_if_present("type", Map.get(url || %{}, "type"))
%{"url" => [attachment_url]}
|> Maps.put_if_present("mediaType", media_type)
|> Maps.put_if_present("type", data["type"])
|> Maps.put_if_present("name", data["name"])
%{"url" => [attachment_url]}
|> Maps.put_if_present("mediaType", media_type)
|> Maps.put_if_present("type", data["type"])
|> Maps.put_if_present("name", data["name"])
else
nil
end
end)
|> Enum.filter(& &1)
Map.put(object, "attachment", attachments)
end
@ -263,12 +269,18 @@ def fix_url(%{"url" => url} = object) when is_map(url) do
def fix_url(%{"type" => object_type, "url" => url} = object)
when object_type in ["Video", "Audio"] and is_list(url) do
first_element = Enum.at(url, 0)
attachment =
Enum.find(url, fn x ->
media_type = x["mediaType"] || x["mimeType"] || ""
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
is_map(x) and String.starts_with?(media_type, ["audio/", "video/"])
end)
link_element =
Enum.find(url, fn x -> is_map(x) and (x["mediaType"] || x["mimeType"]) == "text/html" end)
object
|> Map.put("attachment", [first_element])
|> Map.put("attachment", [attachment])
|> Map.put("url", link_element["href"])
end
@ -446,12 +458,9 @@ def handle_incoming(
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
actor = Containment.get_actor(data)
data =
Map.put(data, "actor", actor)
|> fix_addressing
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
data <- Map.put(data, "actor", actor) |> fix_addressing() do
object = fix_object(object, options)
params = %{
@ -520,66 +529,6 @@ def handle_incoming(
end
end
def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
_options
) do
with %User{local: true} = followed <-
User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
{:ok, %User{} = follower} <-
User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
{:ok, activity} <-
ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
{_, false} <- {:user_locked, User.locked?(followed)},
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <-
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
{:ok, _relationship} <-
FollowingRelationship.update(follower, followed, :follow_accept) do
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed,
object: data,
local: true
})
else
{:user_blocked, true} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: data,
local: true
})
{:follow, {:error, _}} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed,
object: data,
local: true
})
{:user_locked, true} ->
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
:noop
end
ActivityPub.notify_and_stream(activity)
{:ok, activity}
else
_e ->
:error
end
end
def handle_incoming(
%{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
_options
@ -673,7 +622,7 @@ def handle_incoming(
end
def handle_incoming(%{"type" => type} = data, _options)
when type in ["Like", "EmojiReact", "Announce"] do
when type in ~w{Like EmojiReact Announce} do
with :ok <- ObjectValidator.fetch_actor_and_object(data),
{:ok, activity, _meta} <-
Pipeline.common_pipeline(data, local: false) do
@ -684,9 +633,10 @@ def handle_incoming(%{"type" => type} = data, _options)
end
def handle_incoming(
%{"type" => "Update"} = data,
%{"type" => type} = data,
_options
) do
)
when type in ~w{Update Block Follow} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
@ -765,21 +715,6 @@ def handle_incoming(
end
end
def handle_incoming(
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
_options
) do
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
User.unfollow(blocker, blocked)
User.block(blocker, blocked)
{:ok, activity}
else
_e -> :error
end
end
def handle_incoming(
%{
"type" => "Move",

View file

@ -81,6 +81,15 @@ def render("user.json", %{user: user}) do
fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
capabilities =
if is_boolean(user.accepts_chat_messages) do
%{
"acceptsChatMessages" => user.accepts_chat_messages
}
else
%{}
end
%{
"id" => user.ap_id,
"type" => user.actor_type,
@ -101,7 +110,8 @@ def render("user.json", %{user: user}) do
"endpoints" => endpoints,
"attachment" => fields,
"tag" => emoji_tags,
"discoverable" => user.discoverable
"discoverable" => user.discoverable,
"capabilities" => capabilities
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))

View file

@ -47,6 +47,10 @@ def is_list?(_), do: false
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
def visible_for_user?(nil, _), do: false
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
user.ap_id in activity.data["to"] ||
list_ap_id
@ -54,8 +58,6 @@ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{
|> Pleroma.List.member?(user)
end
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
def visible_for_user?(%{local: local} = activity, nil) do
cfg_key =
if local,

View file

@ -206,8 +206,8 @@ def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
end
end
def user_show(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
conn
|> put_view(AccountView)
|> render("show.json", %{user: user})
@ -233,11 +233,11 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
|> render("index.json", %{activities: activities, as: :activity})
end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
godmode = params["godmode"] == "true" || params["godmode"] == true
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
{_, page_size} = page_params(params)
activities =
@ -526,7 +526,7 @@ def disable_mfa(conn, %{"nickname" => nickname}) do
@doc "Show a given user's credentials"
def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
conn
|> put_view(AccountView)
|> render("credentials.json", %{user: user, for: admin})

View file

@ -9,8 +9,6 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
alias Pleroma.ConfigDB
alias Pleroma.Plugs.OAuthScopesPlug
@descriptions Pleroma.Docs.JSON.compile()
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
@ -25,7 +23,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
def descriptions(conn, _params) do
descriptions = Enum.filter(@descriptions, &whitelisted_config?/1)
descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
json(conn, descriptions)
end

View file

@ -40,7 +40,7 @@ def call(%{private: %{open_api_spex: private_data}} = conn, %{
|> List.first()
_ ->
nil
"application/json"
end
private_data = Map.put(private_data, :operation_id, operation_id)

View file

@ -61,7 +61,7 @@ def update_credentials_operation do
description: "Update the user's display and preferences.",
operationId: "AccountController.update_credentials",
security: [%{"oAuth" => ["write:accounts"]}],
requestBody: request_body("Parameters", update_creadentials_request(), required: true),
requestBody: request_body("Parameters", update_credentials_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", Account),
403 => Operation.response("Error", "application/json", ApiError)
@ -203,14 +203,23 @@ def follow_operation do
security: [%{"oAuth" => ["follow", "write:follows"]}],
description: "Follow the given account",
parameters: [
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
Operation.parameter(
:reblogs,
:query,
BooleanLike,
"Receive this account's reblogs in home timeline? Defaults to true."
)
%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
],
requestBody:
request_body(
"Parameters",
%Schema{
type: :object,
properties: %{
reblogs: %Schema{
type: :boolean,
description: "Receive this account's reblogs in home timeline? Defaults to true.",
default: true
}
}
},
required: false
),
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
@ -438,6 +447,7 @@ defp create_request do
}
end
# TODO: This is actually a token respone, but there's no oauth operation file yet.
defp create_response do
%Schema{
title: "AccountCreateResponse",
@ -446,19 +456,25 @@ defp create_response do
properties: %{
token_type: %Schema{type: :string},
access_token: %Schema{type: :string},
scope: %Schema{type: :array, items: %Schema{type: :string}},
created_at: %Schema{type: :integer, format: :"date-time"}
refresh_token: %Schema{type: :string},
scope: %Schema{type: :string},
created_at: %Schema{type: :integer, format: :"date-time"},
me: %Schema{type: :string},
expires_in: %Schema{type: :integer}
},
example: %{
"token_type" => "Bearer",
"access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
"refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",
"created_at" => 1_585_918_714,
"scope" => ["read", "write", "follow", "push"],
"token_type" => "Bearer"
"expires_in" => 600,
"scope" => "read write follow push",
"me" => "https://gensokyo.2hu/users/raymoo"
}
}
end
defp update_creadentials_request do
defp update_credentials_request do
%Schema{
title: "AccountUpdateCredentialsRequest",
description: "POST body for creating an account",
@ -492,6 +508,11 @@ defp update_creadentials_request do
nullable: true,
description: "Whether manual approval of follow requests is required."
},
accepts_chat_messages: %Schema{
allOf: [BooleanLike],
nullable: true,
description: "Whether the user accepts receiving chat messages."
},
fields_attributes: %Schema{
nullable: true,
oneOf: [

View file

@ -4,7 +4,6 @@
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
@ -40,48 +39,6 @@ def confirmation_resend_operation do
}
end
def update_avatar_operation do
%Operation{
tags: ["Accounts"],
summary: "Set/clear user avatar image",
operationId: "PleromaAPI.AccountController.update_avatar",
requestBody:
request_body("Parameters", update_avatar_or_background_request(), required: true),
security: [%{"oAuth" => ["write:accounts"]}],
responses: %{
200 => update_response(),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def update_banner_operation do
%Operation{
tags: ["Accounts"],
summary: "Set/clear user banner image",
operationId: "PleromaAPI.AccountController.update_banner",
requestBody: request_body("Parameters", update_banner_request(), required: true),
security: [%{"oAuth" => ["write:accounts"]}],
responses: %{
200 => update_response()
}
}
end
def update_background_operation do
%Operation{
tags: ["Accounts"],
summary: "Set/clear user background image",
operationId: "PleromaAPI.AccountController.update_background",
security: [%{"oAuth" => ["write:accounts"]}],
requestBody:
request_body("Parameters", update_avatar_or_background_request(), required: true),
responses: %{
200 => update_response()
}
}
end
def favourites_operation do
%Operation{
tags: ["Accounts"],
@ -136,52 +93,4 @@ defp id_param do
required: true
)
end
defp update_avatar_or_background_request do
%Schema{
title: "PleromaAccountUpdateAvatarOrBackgroundRequest",
type: :object,
properties: %{
img: %Schema{
nullable: true,
type: :string,
format: :binary,
description: "Image encoded using `multipart/form-data` or an empty string to clear"
}
}
}
end
defp update_banner_request do
%Schema{
title: "PleromaAccountUpdateBannerRequest",
type: :object,
properties: %{
banner: %Schema{
type: :string,
nullable: true,
format: :binary,
description: "Image encoded using `multipart/form-data` or an empty string to clear"
}
}
}
end
defp update_response do
Operation.response("PleromaAccountUpdateResponse", "application/json", %Schema{
type: :object,
properties: %{
url: %Schema{
type: :string,
format: :uri,
nullable: true,
description: "Image URL"
}
},
example: %{
"url" =>
"https://cofe.party/media/9d0add56-bcb6-4c0f-8225-cbbd0b6dd773/13eadb6972c9ccd3f4ffa3b8196f0e0d38b4d2f27594457c52e52946c054cd9a.gif"
}
})
end
end

View file

@ -84,7 +84,7 @@ def delete_operation do
operationId: "StatusController.delete",
parameters: [id_param()],
responses: %{
200 => empty_object_response(),
200 => status_response(),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}

View file

@ -40,20 +40,53 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
pleroma: %Schema{
type: :object,
properties: %{
allow_following_move: %Schema{type: :boolean},
background_image: %Schema{type: :string, nullable: true},
allow_following_move: %Schema{
type: :boolean,
description: "whether the user allows automatically follow moved following accounts"
},
background_image: %Schema{type: :string, nullable: true, format: :uri},
chat_token: %Schema{type: :string},
confirmation_pending: %Schema{type: :boolean},
confirmation_pending: %Schema{
type: :boolean,
description:
"whether the user account is waiting on email confirmation to be activated"
},
hide_favorites: %Schema{type: :boolean},
hide_followers_count: %Schema{type: :boolean},
hide_followers: %Schema{type: :boolean},
hide_follows_count: %Schema{type: :boolean},
hide_follows: %Schema{type: :boolean},
is_admin: %Schema{type: :boolean},
is_moderator: %Schema{type: :boolean},
hide_followers_count: %Schema{
type: :boolean,
description: "whether the user has follower stat hiding enabled"
},
hide_followers: %Schema{
type: :boolean,
description: "whether the user has follower hiding enabled"
},
hide_follows_count: %Schema{
type: :boolean,
description: "whether the user has follow stat hiding enabled"
},
hide_follows: %Schema{
type: :boolean,
description: "whether the user has follow hiding enabled"
},
is_admin: %Schema{
type: :boolean,
description: "whether the user is an admin of the local instance"
},
is_moderator: %Schema{
type: :boolean,
description: "whether the user is a moderator of the local instance"
},
skip_thread_containment: %Schema{type: :boolean},
tags: %Schema{type: :array, items: %Schema{type: :string}},
unread_conversation_count: %Schema{type: :integer},
tags: %Schema{
type: :array,
items: %Schema{type: :string},
description:
"List of tags being used for things like extra roles or moderation(ie. marking all media as nsfw all)."
},
unread_conversation_count: %Schema{
type: :integer,
description: "The count of unread conversations. Only returned to the account owner."
},
notification_settings: %Schema{
type: :object,
properties: %{
@ -63,7 +96,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
},
relationship: AccountRelationship,
settings_store: %Schema{
type: :object
type: :object,
description:
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
},
accepts_chat_messages: %Schema{type: :boolean, nullable: true},
favicon: %Schema{
type: :string,
format: :uri,
nullable: true,
description: "Favicon image of the user's instance"
}
}
},
@ -71,16 +113,32 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
type: :object,
properties: %{
fields: %Schema{type: :array, items: AccountField},
note: %Schema{type: :string},
note: %Schema{
type: :string,
description:
"Plaintext version of the bio without formatting applied by the backend, used for editing the bio."
},
privacy: VisibilityScope,
sensitive: %Schema{type: :boolean},
pleroma: %Schema{
type: :object,
properties: %{
actor_type: ActorType,
discoverable: %Schema{type: :boolean},
no_rich_text: %Schema{type: :boolean},
show_role: %Schema{type: :boolean}
discoverable: %Schema{
type: :boolean,
description:
"whether the user allows discovery of the account in search results and other services."
},
no_rich_text: %Schema{
type: :boolean,
description:
"whether the HTML tags for rich-text formatting are stripped from all statuses requested from the API."
},
show_role: %Schema{
type: :boolean,
description:
"whether the user wants their role (e.g admin, moderator) to be shown"
}
}
}
}
@ -115,6 +173,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
"is_admin" => false,
"is_moderator" => false,
"skip_thread_containment" => false,
"accepts_chat_messages" => true,
"chat_token" =>
"SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
"unread_conversation_count" => 0,

View file

@ -62,6 +62,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
}
},
content: %Schema{type: :string, format: :html, description: "HTML-encoded status content"},
text: %Schema{
type: :string,
description: "Original unformatted content in plain text",
nullable: true
},
created_at: %Schema{
type: :string,
format: "date-time",
@ -184,6 +189,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
thread_muted: %Schema{
type: :boolean,
description: "`true` if the thread the post belongs to is muted"
},
parent_visible: %Schema{
type: :boolean,
description: "`true` if the parent post is visible to the user"
}
}
},

View file

@ -186,6 +186,7 @@ defp object(draft) do
draft.poll
)
|> Map.put("emoji", emoji)
|> Map.put("source", draft.status)
%__MODULE__{draft | object: object}
end

View file

@ -25,6 +25,13 @@ defmodule Pleroma.Web.CommonAPI do
require Pleroma.Constants
require Logger
def block(blocker, blocked) do
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
{:ok, block}
end
end
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
:ok <- validate_chat_content_length(content, !!maybe_attachment),
@ -94,10 +101,14 @@ def unblock(blocker, blocked) do
def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
with {:ok, follower} <- User.maybe_direct_follow(follower, followed),
{:ok, activity} <- ActivityPub.follow(follower, followed),
with {:ok, follow_data, _} <- Builder.follow(follower, followed),
{:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),
{:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do
{:ok, follower, followed, activity}
if activity.data["state"] == "reject" do
{:error, :rejected}
else
{:ok, follower, followed, activity}
end
end
end

View file

@ -143,7 +143,7 @@ def make_poll_data(%{"poll" => %{"expires_in" => expires_in}} = data)
def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
when is_list(options) do
limits = Pleroma.Config.get([:instance, :poll_limits])
limits = Config.get([:instance, :poll_limits])
with :ok <- validate_poll_expiration(expires_in, limits),
:ok <- validate_poll_options_amount(options, limits),
@ -502,7 +502,7 @@ def maybe_extract_mentions(_), do: []
def make_report_content_html(nil), do: {:ok, {nil, [], []}}
def make_report_content_html(comment) do
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
max_size = Config.get([:instance, :max_report_comment_size], 1000)
if String.length(comment) <= max_size do
{:ok, format_input(comment, "text/plain")}
@ -564,7 +564,7 @@ def validate_character_limit("" = _full_payload, [] = _attachments) do
end
def validate_character_limit(full_payload, _attachments) do
limit = Pleroma.Config.get([:instance, :limit])
limit = Config.get([:instance, :limit])
length = String.length(full_payload)
if length <= limit do

View file

@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.MastodonAPI.MastodonAPIController
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
@ -101,12 +102,7 @@ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
:ok <- TwitterAPI.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
{:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
json(conn, %{
token_type: "Bearer",
access_token: token.token,
scope: app.scopes,
created_at: Token.Utils.format_created_at(token)
})
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
{:error, error} -> json_response(conn, :bad_request, %{error: error})
end
@ -148,6 +144,13 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|> Enum.filter(fn {_, value} -> not is_nil(value) end)
|> Enum.into(%{})
# We use an empty string as a special value to reset
# avatars, banners, backgrounds
user_image_value = fn
"" -> {:ok, nil}
value -> {:ok, value}
end
user_params =
[
:no_rich_text,
@ -160,7 +163,8 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
:show_role,
:skip_thread_containment,
:allow_following_move,
:discoverable
:discoverable,
:accepts_chat_messages
]
|> Enum.reduce(%{}, fn key, acc ->
Maps.put_if_present(acc, key, params[key], &{:ok, truthy_param?(&1)})
@ -168,9 +172,9 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|> Maps.put_if_present(:name, params[:display_name])
|> Maps.put_if_present(:bio, params[:note])
|> Maps.put_if_present(:raw_bio, params[:note])
|> Maps.put_if_present(:avatar, params[:avatar])
|> Maps.put_if_present(:banner, params[:header])
|> Maps.put_if_present(:background, params[:pleroma_background_image])
|> Maps.put_if_present(:avatar, params[:avatar], user_image_value)
|> Maps.put_if_present(:banner, params[:header], user_image_value)
|> Maps.put_if_present(:background, params[:pleroma_background_image], user_image_value)
|> Maps.put_if_present(
:raw_fields,
params[:fields_attributes],
@ -346,7 +350,7 @@ def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
{:error, "Can not follow yourself"}
end
def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do
with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
render(conn, "relationship.json", user: follower, target: followed)
else
@ -385,8 +389,7 @@ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
@doc "POST /api/v1/accounts/:id/block"
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, _user_block} <- User.block(blocker, blocked),
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do
render(conn, "relationship.json", user: blocker, target: blocked)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})

View file

@ -44,6 +44,7 @@ def search2(conn, params), do: do_search(:v2, conn, params)
def search(conn, params), do: do_search(:v1, conn, params)
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
query = String.trim(query)
options = search_options(params, user)
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}

View file

@ -200,11 +200,16 @@ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
@doc "DELETE /api/v1/statuses/:id"
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{})
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
try_render(conn, "show.json",
activity: activity,
for: user,
with_direct_conversation_id: true,
with_source: true
)
else
{:error, :not_found} = e -> e
_e -> render_error(conn, :forbidden, "Can't delete this post")
_e -> {:error, :not_found}
end
end

View file

@ -88,21 +88,20 @@ def direct(%{assigns: %{user: user}} = conn, params) do
)
end
defp restrict_unauthenticated?(true = _local_only) do
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local])
end
defp restrict_unauthenticated?(_) do
Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated])
end
# GET /api/v1/timelines/public
def public(%{assigns: %{user: user}} = conn, params) do
local_only = params[:local]
cfg_key =
if local_only do
:local
else
:federated
end
restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key])
if restrict? and is_nil(user) do
render_error(conn, :unauthorized, "authorization required for timeline view")
if is_nil(user) and restrict_unauthenticated?(local_only) do
fail_on_bad_auth(conn)
else
activities =
params
@ -123,6 +122,10 @@ def public(%{assigns: %{user: user}} = conn, params) do
end
end
defp fail_on_bad_auth(conn) do
render_error(conn, :unauthorized, "authorization required for timeline view")
end
defp hashtag_fetching(params, user, local_only) do
tags =
[params[:tag], params[:any]]
@ -157,15 +160,20 @@ defp hashtag_fetching(params, user, local_only) do
# GET /api/v1/timelines/tag/:tag
def hashtag(%{assigns: %{user: user}} = conn, params) do
local_only = params[:local]
activities = hashtag_fetching(params, user, local_only)
conn
|> add_link_headers(activities, %{"local" => local_only})
|> render("index.json",
activities: activities,
for: user,
as: :activity
)
if is_nil(user) and restrict_unauthenticated?(local_only) do
fail_on_bad_auth(conn)
else
activities = hashtag_fetching(params, user, local_only)
conn
|> add_link_headers(activities, %{"local" => local_only})
|> render("index.json",
activities: activities,
for: user,
as: :activity
)
end
end
# GET /api/v1/timelines/list/:list_id

View file

@ -204,6 +204,18 @@ defp do_render("show.json", %{user: user} = opts) do
%{}
end
favicon =
if Pleroma.Config.get([:instances_favicons, :enabled]) do
user
|> Map.get(:ap_id, "")
|> URI.parse()
|> URI.merge("/")
|> Pleroma.Instances.Instance.get_or_update_favicon()
|> MediaProxy.url()
else
nil
end
%{
id: to_string(user.id),
username: username_from_nickname(user.nickname),
@ -245,7 +257,9 @@ defp do_render("show.json", %{user: user} = opts) do
hide_favorites: user.hide_favorites,
relationship: relationship,
skip_thread_containment: user.skip_thread_containment,
background_image: image_url(user.background) |> MediaProxy.url()
background_image: image_url(user.background) |> MediaProxy.url(),
accepts_chat_messages: user.accepts_chat_messages,
favicon: favicon
}
}
|> maybe_put_role(user, opts[:for])

View file

@ -34,10 +34,14 @@ def render("show.json", _) do
background_upload_limit: Keyword.get(instance, :background_upload_limit),
banner_upload_limit: Keyword.get(instance, :banner_upload_limit),
background_image: Keyword.get(instance, :background_image),
chat_limit: Keyword.get(instance, :chat_limit),
description_limit: Keyword.get(instance, :description_limit),
pleroma: %{
metadata: %{
account_activation_required: Keyword.get(instance, :account_activation_required),
features: features(),
federation: federation()
federation: federation(),
fields_limits: fields_limits()
},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
@ -88,4 +92,13 @@ def federation do
end
|> Map.put(:enabled, Config.get([:instance, :federating]))
end
def fields_limits do
%{
max_fields: Config.get([:instance, :max_account_fields]),
max_remote_fields: Config.get([:instance, :max_remote_account_fields]),
name_length: Config.get([:instance, :account_field_name_length]),
value_length: Config.get([:instance, :account_field_value_length])
}
end
end

View file

@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
# TODO: Add cached version.
defp get_replied_to_activities([]), do: %{}
@ -333,6 +333,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
reblog: nil,
card: card,
content: content_html,
text: opts[:with_source] && object.data["source"],
created_at: created_at,
reblogs_count: announcement_count,
replies_count: object.data["repliesCount"] || 0,
@ -364,7 +365,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
expires_at: expires_at,
direct_conversation_id: direct_conversation_id,
thread_muted: thread_muted?,
emoji_reactions: emoji_reactions
emoji_reactions: emoji_reactions,
parent_visible: visible_for_user?(reply_to, opts[:for])
}
}
end

View file

@ -106,7 +106,7 @@ def filename(url_or_path) do
def build_url(sig_base64, url_base64, filename \\ nil) do
[
Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()),
Config.get([:media_proxy, :base_url], Web.base_url()),
"proxy",
sig_base64,
url_base64,

View file

@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.MFAController do
alias Pleroma.Web.Auth.TOTPAuthenticator
alias Pleroma.Web.OAuth.MFAView, as: View
alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.Token
plug(:fetch_session when action in [:show, :verify])
@ -74,7 +75,7 @@ def challenge(conn, %{"mfa_token" => mfa_token} = params) do
{:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
{:ok, _} <- validates_challenge(user, params),
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build(user, token))
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
_error ->
conn

View file

@ -5,4 +5,13 @@
defmodule Pleroma.Web.OAuth.MFAView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
alias Pleroma.MFA
def render("mfa_response.json", %{token: token, user: user}) do
%{
error: "mfa_required",
mfa_token: token.token,
supported_challenge_types: MFA.supported_methods(user)
}
end
end

View file

@ -17,6 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.MFAController
alias Pleroma.Web.OAuth.MFAView
alias Pleroma.Web.OAuth.OAuthView
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
@ -233,9 +235,7 @@ def token_exchange(
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
{:ok, token} <- RefreshToken.grant(token) do
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
json(conn, Token.Response.build(user, token, response_attrs))
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
_error -> render_invalid_credentials_error(conn)
end
@ -247,9 +247,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth) do
response_attrs = %{created_at: Token.Utils.format_created_at(token)}
json(conn, Token.Response.build(user, token, response_attrs))
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
error ->
handle_token_exchange_error(conn, error)
@ -267,7 +265,7 @@ def token_exchange(
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build(user, token))
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
error ->
handle_token_exchange_error(conn, error)
@ -290,7 +288,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"}
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
{:ok, token} <- Token.exchange_token(app, auth) do
json(conn, Token.Response.build_for_client_credentials(token))
json(conn, OAuthView.render("token.json", %{token: token}))
else
_error ->
handle_token_exchange_error(conn, :invalid_credentails)
@ -548,7 +546,7 @@ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
defp build_and_response_mfa_token(user, auth) do
with {:ok, token} <- MFA.Token.create_token(user, auth) do
Token.Response.build_for_mfa_token(user, token)
MFAView.render("mfa_response.json", %{token: token, user: user})
end
end

View file

@ -5,4 +5,26 @@
defmodule Pleroma.Web.OAuth.OAuthView do
use Pleroma.Web, :view
import Phoenix.HTML.Form
alias Pleroma.Web.OAuth.Token.Utils
def render("token.json", %{token: token} = opts) do
response = %{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
expires_in: expires_in(),
scope: Enum.join(token.scopes, " "),
created_at: Utils.format_created_at(token)
}
if user = opts[:user] do
response
|> Map.put(:me, user.ap_id)
else
response
end
end
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end

View file

@ -1,45 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Response do
@moduledoc false
alias Pleroma.MFA
alias Pleroma.User
alias Pleroma.Web.OAuth.Token.Utils
@doc false
def build(%User{} = user, token, opts \\ %{}) do
%{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
expires_in: expires_in(),
scope: Enum.join(token.scopes, " "),
me: user.ap_id
}
|> Map.merge(opts)
end
def build_for_client_credentials(token) do
%{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
created_at: Utils.format_created_at(token),
expires_in: expires_in(),
scope: Enum.join(token.scopes, " ")
}
end
def build_for_mfa_token(user, mfa_token) do
%{
error: "mfa_required",
mfa_token: mfa_token.token,
supported_challenge_types: MFA.supported_methods(user)
}
end
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end

View file

@ -8,7 +8,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
alias Ecto.Changeset
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
@ -35,17 +34,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
# Note: the following actions are not permission-secured in Mastodon:
when action in [
:update_avatar,
:update_banner,
:update_background
]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
@ -68,56 +56,6 @@ def confirmation_resend(conn, params) do
end
end
@doc "PATCH /api/v1/pleroma/accounts/update_avatar"
def update_avatar(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
{:ok, _user} =
user
|> Changeset.change(%{avatar: nil})
|> User.update_and_set_cache()
json(conn, %{url: nil})
end
def update_avatar(%{assigns: %{user: user}, body_params: params} = conn, _params) do
{:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
{:ok, _user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
%{"url" => [%{"href" => href} | _]} = data
json(conn, %{url: href})
end
@doc "PATCH /api/v1/pleroma/accounts/update_banner"
def update_banner(%{assigns: %{user: user}, body_params: %{banner: ""}} = conn, _) do
with {:ok, _user} <- User.update_banner(user, %{}) do
json(conn, %{url: nil})
end
end
def update_banner(%{assigns: %{user: user}, body_params: params} = conn, _) do
with {:ok, object} <- ActivityPub.upload(%{img: params[:banner]}, type: :banner),
{:ok, _user} <- User.update_banner(user, object.data) do
%{"url" => [%{"href" => href} | _]} = object.data
json(conn, %{url: href})
end
end
@doc "PATCH /api/v1/pleroma/accounts/update_background"
def update_background(%{assigns: %{user: user}, body_params: %{img: ""}} = conn, _) do
with {:ok, _user} <- User.update_background(user, %{}) do
json(conn, %{url: nil})
end
end
def update_background(%{assigns: %{user: user}, body_params: params} = conn, _) do
with {:ok, object} <- ActivityPub.upload(params, type: :background),
{:ok, _user} <- User.update_background(user, object.data) do
%{"url" => [%{"href" => href} | _]} = object.data
json(conn, %{url: href})
end
end
@doc "GET /api/v1/pleroma/accounts/:id/favourites"
def favourites(%{assigns: %{account: %{hide_favorites: true}}} = conn, _params) do
render_error(conn, :forbidden, "Can't get favorites")

View file

@ -3,14 +3,15 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.Instance do
alias Pleroma.Plugs.InstanceStatic
alias Pleroma.Web.MastodonAPI.InstanceView
alias Pleroma.Web.Nodeinfo.Nodeinfo
alias Pleroma.Web.Preload.Providers.Provider
@behaviour Provider
@instance_url :"/api/v1/instance"
@panel_url :"/instance/panel.html"
@nodeinfo_url :"/nodeinfo/2.0"
@instance_url "/api/v1/instance"
@panel_url "/instance/panel.html"
@nodeinfo_url "/nodeinfo/2.0.json"
@impl Provider
def generate_terms(_params) do
@ -27,7 +28,7 @@ defp build_info_tag(acc) do
end
defp build_panel_tag(acc) do
instance_path = Path.join(:code.priv_dir(:pleroma), "static/instance/panel.html")
instance_path = InstanceStatic.file_path(@panel_url |> to_string())
if File.exists?(instance_path) do
panel_data = File.read!(instance_path)

View file

@ -1,24 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.StatusNet do
alias Pleroma.Web.Preload.Providers.Provider
alias Pleroma.Web.TwitterAPI.UtilView
@behaviour Provider
@config_url :"/api/statusnet/config.json"
@impl Provider
def generate_terms(_params) do
%{}
|> build_config_tag()
end
defp build_config_tag(acc) do
instance = Pleroma.Config.get(:instance)
info_data = UtilView.status_net_config(instance)
Map.put(acc, @config_url, info_data)
end
end

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.Preload.Providers.Timelines do
alias Pleroma.Web.Preload.Providers.Provider
@behaviour Provider
@public_url :"/api/v1/timelines/public"
@public_url "/api/v1/timelines/public"
@impl Provider
def generate_terms(params) do

View file

@ -3,11 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Preload.Providers.User do
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.Preload.Providers.Provider
@behaviour Provider
@account_url :"/api/v1/accounts"
@account_url_base "/api/v1/accounts"
@impl Provider
def generate_terms(%{user: user}) do
@ -16,10 +17,10 @@ def generate_terms(%{user: user}) do
def generate_terms(_params), do: %{}
def build_accounts_tag(acc, nil), do: acc
def build_accounts_tag(acc, user) do
def build_accounts_tag(acc, %User{} = user) do
account_data = AccountView.render("show.json", %{user: user, for: user})
Map.put(acc, @account_url, account_data)
Map.put(acc, "#{@account_url_base}/#{user.id}", account_data)
end
def build_accounts_tag(acc, _), do: acc
end

View file

@ -86,7 +86,10 @@ defp parse_url(url) do
end
try do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: opts)
rich_media_agent = Pleroma.Application.user_agent() <> "; Bot"
{:ok, %Tesla.Env{body: html}} =
Pleroma.HTTP.get(url, [{"user-agent", rich_media_agent}], adapter: opts)
html
|> parse_html()

View file

@ -328,10 +328,6 @@ defmodule Pleroma.Web.Router do
delete("/statuses/:id/reactions/:emoji", EmojiReactionController, :delete)
post("/notifications/read", NotificationController, :mark_as_read)
patch("/accounts/update_avatar", AccountController, :update_avatar)
patch("/accounts/update_banner", AccountController, :update_banner)
patch("/accounts/update_background", AccountController, :update_background)
get("/mascot", MascotController, :show)
put("/mascot", MascotController, :update)
@ -516,10 +512,6 @@ defmodule Pleroma.Web.Router do
scope "/api", Pleroma.Web do
pipe_through(:config)
get("/help/test", TwitterAPI.UtilController, :help_test)
post("/help/test", TwitterAPI.UtilController, :help_test)
get("/statusnet/config", TwitterAPI.UtilController, :config)
get("/statusnet/version", TwitterAPI.UtilController, :version)
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
end

View file

@ -104,7 +104,9 @@ def stream(topics, items) do
:ok
end
def filtered_by_user?(%User{} = user, %Activity{} = item) do
def filtered_by_user?(user, item, streamed_type \\ :activity)
def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
%{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
@ -116,6 +118,9 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) do
true <-
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
true <-
!(streamed_type == :activity && item.data["type"] == "Announce" &&
parent.data["actor"] == user.ap_id),
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor),
@ -130,8 +135,8 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) do
end
end
def filtered_by_user?(%User{} = user, %Notification{activity: activity}) do
filtered_by_user?(user, activity)
def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do
filtered_by_user?(user, activity, :notification)
end
defp do_stream("direct", item) do

View file

@ -13,9 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.UtilView
alias Pleroma.Web.WebFinger
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
@ -42,12 +40,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
def help_test(conn, _params) do
json(conn, "ok")
end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
with %User{} = user <- User.get_cached_by_nickname(nick),
avatar = User.avatar_url(user) do
@ -89,80 +81,14 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_
end
end
def config(%{assigns: %{format: "xml"}} = conn, _params) do
instance = Pleroma.Config.get(:instance)
response = UtilView.status_net_config(instance)
conn
|> put_resp_content_type("application/xml")
|> send_resp(200, response)
end
def config(conn, _params) do
instance = Pleroma.Config.get(:instance)
vapid_public_key = Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
uploadlimit = %{
uploadlimit: to_string(Keyword.get(instance, :upload_limit)),
avatarlimit: to_string(Keyword.get(instance, :avatar_upload_limit)),
backgroundlimit: to_string(Keyword.get(instance, :background_upload_limit)),
bannerlimit: to_string(Keyword.get(instance, :banner_upload_limit))
}
data = %{
name: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(instance, :limit)),
uploadlimit: uploadlimit,
closed: bool_to_val(Keyword.get(instance, :registrations_open), "0", "1"),
private: bool_to_val(Keyword.get(instance, :public, true), "0", "1"),
vapidPublicKey: vapid_public_key,
accountActivationRequired:
bool_to_val(Keyword.get(instance, :account_activation_required, false)),
invitesEnabled: bool_to_val(Keyword.get(instance, :invites_enabled, false)),
safeDMMentionsEnabled: bool_to_val(Pleroma.Config.get([:instance, :safe_dm_mentions]))
}
managed_config = Keyword.get(instance, :managed_config)
data =
if managed_config do
pleroma_fe = Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
Map.put(data, "pleromafe", pleroma_fe)
else
data
end
json(conn, %{site: data})
end
defp bool_to_val(true), do: "1"
defp bool_to_val(_), do: "0"
defp bool_to_val(true, val, _), do: val
defp bool_to_val(_, _, val), do: val
def frontend_configurations(conn, _params) do
config =
Pleroma.Config.get(:frontend_configurations, %{})
Config.get(:frontend_configurations, %{})
|> Enum.into(%{})
json(conn, config)
end
def version(%{assigns: %{format: "xml"}} = conn, _params) do
version = Pleroma.Application.named_version()
conn
|> put_resp_content_type("application/xml")
|> send_resp(200, "<version>#{version}</version>")
end
def version(conn, _params) do
json(conn, Pleroma.Application.named_version())
end
def emoji(conn, _params) do
emoji =
Enum.reduce(Emoji.get_all(), %{}, fn {code, %Emoji{file: file, tags: tags}}, acc ->

View file

@ -86,7 +86,7 @@ def initial_state(token, user, custom_emojis) do
"video\/mp4"
]
},
settings: user.settings || @default_settings,
settings: user.mastofe_settings || @default_settings,
push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),

View file

@ -11,13 +11,12 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
@impl Oban.Worker
def perform(
%{
def perform(%Job{
args: %{
"op" => "cleanup_attachments",
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
},
_job
) do
}
}) do
attachments
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|> fetch_objects
@ -28,7 +27,7 @@ def perform(
{:ok, :success}
end
def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: {:ok, :skip}
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
defp do_clean({object_ids, attachment_urls}) do
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])

View file

@ -11,59 +11,59 @@ defmodule Pleroma.Workers.BackgroundWorker do
@impl Oban.Worker
def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do
def perform(%Job{args: %{"op" => "deactivate_user", "user_id" => user_id, "status" => status}}) do
user = User.get_cached_by_id(user_id)
User.perform(:deactivate_async, user, status)
end
def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do
def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id)
User.perform(:delete, user)
end
def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do
def perform(%Job{args: %{"op" => "force_password_reset", "user_id" => user_id}}) do
user = User.get_cached_by_id(user_id)
User.perform(:force_password_reset, user)
end
def perform(
%{
def perform(%Job{
args: %{
"op" => "blocks_import",
"blocker_id" => blocker_id,
"blocked_identifiers" => blocked_identifiers
},
_job
) do
}
}) do
blocker = User.get_cached_by_id(blocker_id)
{:ok, User.perform(:blocks_import, blocker, blocked_identifiers)}
end
def perform(
%{
def perform(%Job{
args: %{
"op" => "follow_import",
"follower_id" => follower_id,
"followed_identifiers" => followed_identifiers
},
_job
) do
}
}) do
follower = User.get_cached_by_id(follower_id)
{:ok, User.perform(:follow_import, follower, followed_identifiers)}
end
def perform(%{"op" => "media_proxy_preload", "message" => message}, _job) do
def perform(%Job{args: %{"op" => "media_proxy_preload", "message" => message}}) do
MediaProxyWarmingPolicy.perform(:preload, message)
end
def perform(%{"op" => "media_proxy_prefetch", "url" => url}, _job) do
def perform(%Job{args: %{"op" => "media_proxy_prefetch", "url" => url}}) do
MediaProxyWarmingPolicy.perform(:prefetch, url)
end
def perform(%{"op" => "fetch_data_for_activity", "activity_id" => activity_id}, _job) do
def perform(%Job{args: %{"op" => "fetch_data_for_activity", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id)
Pleroma.Web.RichMedia.Helpers.perform(:fetch, activity)
end
def perform(%{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}, _) do
def perform(%Job{
args: %{"op" => "move_following", "origin_id" => origin_id, "target_id" => target_id}
}) do
origin = User.get_cached_by_id(origin_id)
target = User.get_cached_by_id(target_id)

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Workers.Cron.ClearOauthTokenWorker do
alias Pleroma.Web.OAuth.Token
@impl Oban.Worker
def perform(_opts, _job) do
def perform(_job) do
if Config.get([:oauth2, :clean_expired_tokens], false) do
Token.delete_expired_tokens()
else

View file

@ -19,7 +19,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do
require Logger
@impl Oban.Worker
def perform(_opts, _job) do
def perform(_job) do
config = Config.get([:email_notifications, :digest])
if config[:active] do

View file

@ -12,7 +12,7 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do
use Pleroma.Workers.WorkerHelper, queue: "new_users_digest"
@impl Oban.Worker
def perform(_args, _job) do
def perform(_job) do
if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do
today = NaiveDateTime.utc_now() |> Timex.beginning_of_day()

View file

@ -20,7 +20,7 @@ defmodule Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker do
@interval :timer.minutes(1)
@impl Oban.Worker
def perform(_opts, _job) do
def perform(_job) do
if Config.get([ActivityExpiration, :enabled]) do
Enum.each(ActivityExpiration.due_expirations(@interval), &delete_activity/1)
else

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Workers.Cron.StatsWorker do
use Oban.Worker, queue: "background"
@impl Oban.Worker
def perform(_opts, _job) do
def perform(_job) do
Pleroma.Stats.do_collect()
end
end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.Workers.MailerWorker do
use Pleroma.Workers.WorkerHelper, queue: "mailer"
@impl Oban.Worker
def perform(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}, _job) do
def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
encoded_email
|> Base.decode64!()
|> :erlang.binary_to_term()

View file

@ -8,17 +8,17 @@ defmodule Pleroma.Workers.PublisherWorker do
use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing"
def backoff(attempt) when is_integer(attempt) do
def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5)
end
@impl Oban.Worker
def perform(%{"op" => "publish", "activity_id" => activity_id}, _job) do
def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do
activity = Activity.get_by_id(activity_id)
Federator.perform(:publish, activity)
end
def perform(%{"op" => "publish_one", "module" => module_name, "params" => params}, _job) do
def perform(%Job{args: %{"op" => "publish_one", "module" => module_name, "params" => params}}) do
params = Map.new(params, fn {k, v} -> {String.to_atom(k), v} end)
Federator.perform(:publish_one, String.to_atom(module_name), params)
end

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Workers.ReceiverWorker do
use Pleroma.Workers.WorkerHelper, queue: "federator_incoming"
@impl Oban.Worker
def perform(%{"op" => "incoming_ap_doc", "params" => params}, _job) do
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
Federator.perform(:incoming_ap_doc, params)
end
end

View file

@ -8,13 +8,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do
use Pleroma.Workers.WorkerHelper, queue: "remote_fetcher"
@impl Oban.Worker
def perform(
%{
"op" => "fetch_remote",
"id" => id
} = args,
_job
) do
def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do
{:ok, _object} = Fetcher.fetch_object_from_id(id, depth: args["depth"])
end
end

Some files were not shown because too many files have changed in this diff Show more