Merge remote-tracking branch 'origin/develop' into global-status-expiration

This commit is contained in:
Egor Kislitsyn 2020-03-03 00:32:34 +04:00
commit 0f386110c6
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
1688 changed files with 6828 additions and 1839 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
*.ex diff=elixir
*.exs diff=elixir

2
.gitignore vendored
View file

@ -47,3 +47,5 @@ docs/generated_config.md
.idea .idea
pleroma.iml pleroma.iml
# asdf
.tool-versions

View file

@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Security
- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
### Removed ### Removed
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`
- **Breaking**: OStatus protocol support - **Breaking**: OStatus protocol support
@ -21,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default - **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default
- **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features. - **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features.
- **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema. - **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema.
- **Breaking:** `:instance, no_attachment_links` has been replaced with `:instance, attachment_links` which still takes a boolean value but doesn't use double negative language.
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings) - Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler - Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
- Enabled `:instance, extended_nickname_format` in the default config - Enabled `:instance, extended_nickname_format` in the default config
@ -35,7 +39,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Logger: default log level changed from `warn` to `info`. - Logger: default log level changed from `warn` to `info`.
- Config mix task `migrate_to_db` truncates `config` table before migrating the config file. - Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
- MFR policy to set global expiration for all local Create activities - MFR policy to set global expiration for all local Create activities
- Default to `prepare: :unnamed` in the database configuration.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -57,6 +61,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Render whole status in grouped reports - 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: 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. - 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.
- Mastodon API: Limit timeline requests to 3 per timeline per 500ms per user/ip by default.
</details> </details>
### Added ### Added
@ -73,7 +78,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- User notification settings: Add `privacy_option` option. - User notification settings: Add `privacy_option` option.
- Support for custom Elixir modules (such as MRF policies) - Support for custom Elixir modules (such as MRF policies)
- User settings: Add _This account is a_ option. - User settings: Add _This account is a_ option.
- A new users admin digest email
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`). - OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
- Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches.
- ActivityPub: support for `replies` collection (output for outgoing federation & fetching on incoming federation).
- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -101,6 +110,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `feed` option for user atom feed. - Configuration: `feed` option for user atom feed.
- Pleroma API: Add Emoji reactions - Pleroma API: Add Emoji reactions
- Admin API: Add `/api/pleroma/admin/instances/:instance/statuses` - lists all statuses from a given instance - Admin API: Add `/api/pleroma/admin/instances/:instance/statuses` - lists all statuses from a given instance
- Admin API: Add `/api/pleroma/admin/users/:nickname/statuses` - lists all statuses from a given user
- Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users - Admin API: `PATCH /api/pleroma/users/confirm_email` to confirm email for multiple users, `PATCH /api/pleroma/users/resend_confirmation_email` to resend confirmation email for multiple users
- ActivityPub: Configurable `type` field of the actors. - ActivityPub: Configurable `type` field of the actors.
- Mastodon API: `/api/v1/accounts/:id` has `source/pleroma/actor_type` field. - Mastodon API: `/api/v1/accounts/:id` has `source/pleroma/actor_type` field.
@ -115,6 +125,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `feed.logo` option for tag feed. - Configuration: `feed.logo` option for tag feed.
- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag. - Tag feed: `/tags/:tag.rss` - list public statuses by hashtag.
- Mastodon API: Add `reacted` property to `emoji_reactions` - Mastodon API: Add `reacted` property to `emoji_reactions`
- Pleroma API: Add reactions for a single emoji.
- ActivityPub: `[:activitypub, :note_replies_output_limit]` setting sets the number of note self-replies to output on outgoing federation.
- Admin API: `GET /api/pleroma/admin/stats` to get status count by visibility scope
- Admin API: `GET /api/pleroma/admin/statuses` - list all statuses (accepts `godmode` and `local_only`)
</details> </details>
### Fixed ### Fixed

View file

@ -219,6 +219,8 @@
max_expiration: 365 * 24 * 60 * 60 max_expiration: 365 * 24 * 60 * 60
}, },
registrations_open: true, registrations_open: true,
invites_enabled: false,
account_activation_required: false,
federating: true, federating: true,
federation_incoming_replies_max_depth: 100, federation_incoming_replies_max_depth: 100,
federation_reachability_timeout_days: 7, federation_reachability_timeout_days: 7,
@ -241,7 +243,7 @@
mrf_transparency_exclusions: [], mrf_transparency_exclusions: [],
autofollowed_nicknames: [], autofollowed_nicknames: [],
max_pinned_statuses: 1, max_pinned_statuses: 1,
no_attachment_links: true, attachment_links: false,
welcome_user_nickname: nil, welcome_user_nickname: nil,
welcome_message: nil, welcome_message: nil,
max_report_comment_size: 1000, max_report_comment_size: 1000,
@ -326,7 +328,9 @@
unfollow_blocked: true, unfollow_blocked: true,
outgoing_blocks: true, outgoing_blocks: true,
follow_handshake_timeout: 500, follow_handshake_timeout: 500,
sign_object_fetches: true note_replies_output_limit: 5,
sign_object_fetches: true,
authorized_fetch_mode: false
config :pleroma, :streamer, config :pleroma, :streamer,
workers: 3, workers: 3,
@ -400,6 +404,8 @@
config :phoenix, :json_library, Jason config :phoenix, :json_library, Jason
config :phoenix, :filter_parameters, ["password", "confirm"]
config :pleroma, :gopher, config :pleroma, :gopher,
enabled: false, enabled: false,
ip: {0, 0, 0, 0}, ip: {0, 0, 0, 0},
@ -482,13 +488,16 @@
transmogrifier: 20, transmogrifier: 20,
scheduled_activities: 10, scheduled_activities: 10,
background: 5, background: 5,
attachments_cleanup: 5 remote_fetcher: 2,
attachments_cleanup: 5,
new_users_digest: 1
], ],
crontab: [ crontab: [
{"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker}, {"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
{"0 * * * *", Pleroma.Workers.Cron.StatsWorker}, {"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
{"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker}, {"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker} {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
] ]
config :pleroma, :workers, config :pleroma, :workers,
@ -562,6 +571,8 @@
text_muted_color: "#b9b9ba" text_muted_color: "#b9b9ba"
} }
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: false
config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics" config :prometheus, Pleroma.Web.Endpoint.MetricsExporter, path: "/api/pleroma/app_metrics"
config :pleroma, Pleroma.ScheduledActivity, config :pleroma, Pleroma.ScheduledActivity,
@ -590,6 +601,7 @@
config :pleroma, :rate_limit, config :pleroma, :rate_limit,
authentication: {60_000, 15}, authentication: {60_000, 15},
timeline: {500, 3},
search: [{1000, 10}, {1000, 30}], search: [{1000, 10}, {1000, 30}],
app_account_creation: {1_800_000, 25}, app_account_creation: {1_800_000, 25},
relations_actions: {10_000, 10}, relations_actions: {10_000, 10},
@ -614,6 +626,10 @@
config :pleroma, configurable_from_database: false config :pleroma, configurable_from_database: false
config :pleroma, Pleroma.Repo,
parameters: [gin_fuzzy_search_limit: "500"],
prepare: :unnamed
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -101,7 +101,7 @@
%{ %{
key: :versions, key: :versions,
type: {:list, :atom}, type: {:list, :atom},
description: "List of TLS version to use", description: "List of TLS versions to use",
suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"] suggestions: [:tlsv1, ":tlsv1.1", ":tlsv1.2"]
} }
] ]
@ -534,7 +534,8 @@
%{ %{
key: :description, key: :description,
type: :string, type: :string,
description: "The instance's description, can be seen in nodeinfo and /api/v1/instance", description:
"The instance's description. It can be seen in nodeinfo and `/api/v1/instance`",
suggestions: [ suggestions: [
"Very cool instance" "Very cool instance"
] ]
@ -637,29 +638,31 @@
%{ %{
key: :registrations_open, key: :registrations_open,
type: :boolean, type: :boolean,
description: "Enable registrations for anyone, invitations can be enabled when `false`" description:
"Enable registrations for anyone. Invitations require this setting to be disabled."
}, },
%{ %{
key: :invites_enabled, key: :invites_enabled,
type: :boolean, type: :boolean,
description: "Enable user invitations for admins (depends on `registrations_open: false`)" description:
"Enable user invitations for admins (depends on `registrations_open` being disabled)."
}, },
%{ %{
key: :account_activation_required, key: :account_activation_required,
type: :boolean, type: :boolean,
description: "Require users to confirm their emails before signing in" description: "Require users to confirm their emails before signing in."
}, },
%{ %{
key: :federating, key: :federating,
type: :boolean, type: :boolean,
description: "Enable federation with other instances" description: "Enable federation with other instances."
}, },
%{ %{
key: :federation_incoming_replies_max_depth, key: :federation_incoming_replies_max_depth,
label: "Fed. incoming replies max depth", label: "Fed. incoming replies max depth",
type: :integer, type: :integer,
description: description:
"Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while" <> "Max. depth of reply-to and reply 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.", " 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.",
suggestions: [ suggestions: [
100 100
@ -761,14 +764,14 @@
key: :extended_nickname_format, key: :extended_nickname_format,
type: :boolean, type: :boolean,
description: description:
"Set to `true` to use extended local nicknames format (allows underscores/dashes)." <> "Enable to use extended local nicknames format (allows underscores/dashes)." <>
" This will break federation with older software for theses nicknames." " This will break federation with older software for theses nicknames."
}, },
%{ %{
key: :cleanup_attachments, key: :cleanup_attachments,
type: :boolean, type: :boolean,
description: """ description: """
"Set to `true` to remove associated attachments when status is removed. Enable to remove associated attachments when status is removed.
This will not affect duplicates and attachments without status. This will not affect duplicates and attachments without status.
Enabling this will increase load to database when deleting statuses on larger instances. Enabling this will increase load to database when deleting statuses on larger instances.
""" """
@ -796,10 +799,9 @@
] ]
}, },
%{ %{
key: :no_attachment_links, key: :attachment_links,
type: :boolean, type: :boolean,
description: description: "Enable to automatically add attachment link text to statuses"
"Set to `true` to disable automatically adding attachment link text to statuses"
}, },
%{ %{
key: :welcome_message, key: :welcome_message,
@ -830,14 +832,14 @@
key: :safe_dm_mentions, key: :safe_dm_mentions,
type: :boolean, type: :boolean,
description: description:
"If set to `true`, only mentions at the beginning of a post will be used to address people in direct messages." <> "If enabled, only mentions at the beginning of a post will be used to address people in direct messages." <>
" This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\")." <> " This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\")." <>
" Default: `false`" " Default: disabled"
}, },
%{ %{
key: :healthcheck, key: :healthcheck,
type: :boolean, type: :boolean,
description: "If set to `true`, system data will be shown on /api/pleroma/healthcheck" description: "If enabled, system data will be shown on `/api/pleroma/healthcheck`"
}, },
%{ %{
key: :remote_post_retention_days, key: :remote_post_retention_days,
@ -867,7 +869,7 @@
%{ %{
key: :skip_thread_containment, key: :skip_thread_containment,
type: :boolean, type: :boolean,
description: "Skip filter out broken threads. Default: `true`" description: "Skip filtering out broken threads. Default: enabled"
}, },
%{ %{
key: :limit_to_local_content, key: :limit_to_local_content,
@ -1159,17 +1161,15 @@
key: :alwaysShowSubjectInput, key: :alwaysShowSubjectInput,
label: "Always show subject input", label: "Always show subject input",
type: :boolean, type: :boolean,
description: "When set to `false`, auto-hide the subject field when it's empty" description: "When disabled, auto-hide the subject field if it's empty"
}, },
%{ %{
key: :logoMask, key: :logoMask,
label: "Logo mask", label: "Logo mask",
type: :boolean, type: :boolean,
description: description:
"By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, " <> "By default it assumes logo used will be monochrome with alpha channel to be compatible with both light and dark themes. " <>
"so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via CSS3 Masking. " <> "If you want a colorful logo you must disable logoMask."
"Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. " <>
"If you really want colorful logo - it can be done by setting logoMask to false."
}, },
%{ %{
key: :logoMargin, key: :logoMargin,
@ -1183,13 +1183,13 @@
%{ %{
key: :stickers, key: :stickers,
type: :boolean, type: :boolean,
description: "Enables/disables stickers." description: "Enables stickers."
}, },
%{ %{
key: :enableEmojiPicker, key: :enableEmojiPicker,
label: "Emoji picker", label: "Emoji picker",
type: :boolean, type: :boolean,
description: "Enables/disables emoji picker." description: "Enables emoji picker."
} }
] ]
}, },
@ -1297,14 +1297,14 @@
%{ %{
key: :media_removal, key: :media_removal,
type: {:list, :string}, type: {:list, :string},
description: "List of instances to remove medias from", description: "List of instances to strip media attachments from",
suggestions: ["example.com", "*.example.com"] suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
key: :media_nsfw, key: :media_nsfw,
label: "Media NSFW", label: "Media NSFW",
type: {:list, :string}, type: {:list, :string},
description: "List of instances to put medias as NSFW (sensitive) from", description: "List of instances to tag all media as NSFW (sensitive) from",
suggestions: ["example.com", "*.example.com"] suggestions: ["example.com", "*.example.com"]
}, },
%{ %{
@ -1438,21 +1438,21 @@
key: :reject, key: :reject,
type: [:string, :regex], type: [:string, :regex],
description: description:
"A list of patterns which result in message being rejected, each pattern can be a string or a regular expression.", "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] suggestions: ["foo", ~r/foo/iu]
}, },
%{ %{
key: :federated_timeline_removal, key: :federated_timeline_removal,
type: [:string, :regex], type: [:string, :regex],
description: 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.", "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] suggestions: ["foo", ~r/foo/iu]
}, },
%{ %{
key: :replace, key: :replace,
type: [{:tuple, :string, :string}, {:tuple, :regex, :string}], type: [{:tuple, :string, :string}, {:tuple, :regex, :string}],
description: description:
"A list of tuples containing {pattern, replacement}, pattern can be a string or a regular expression.", "A list of tuples containing {pattern, replacement}. Each pattern can be a string or a regular expression.",
suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}] suggestions: [{"foo", "bar"}, {~r/foo/iu, "bar"}]
} }
] ]
@ -1467,7 +1467,7 @@
%{ %{
key: :actors, key: :actors,
type: {:list, :string}, type: {:list, :string},
description: "A list of actors, for which to drop any posts mentioning", description: "A list of actors for which any post mentioning them will be dropped.",
suggestions: ["actor1", "actor2"] suggestions: ["actor1", "actor2"]
} }
] ]
@ -1630,160 +1630,6 @@
} }
] ]
}, },
%{
group: :pleroma,
key: Pleroma.Web.Endpoint,
type: :group,
description: "Phoenix endpoint configuration",
children: [
%{
key: :http,
label: "HTTP",
type: {:keyword, :integer, :tuple},
description: "http protocol configuration",
suggestions: [
port: 8080,
ip: {127, 0, 0, 1}
],
children: [
%{
key: :dispatch,
type: {:list, :tuple},
description: "dispatch settings",
suggestions: [
{:_,
[
{"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
{"/websocket", Phoenix.Endpoint.CowboyWebSocket,
{Phoenix.Transports.WebSocket,
{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}},
{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
]}
# end copied from config.exs
]
},
%{
key: :ip,
label: "IP",
type: :tuple,
description: "ip",
suggestions: [
{0, 0, 0, 0}
]
},
%{
key: :port,
type: :integer,
description: "port",
suggestions: [
2020
]
}
]
},
%{
key: :url,
label: "URL",
type: {:keyword, :string, :integer},
description: "configuration for generating urls",
suggestions: [
host: "example.com",
port: 2020,
scheme: "https"
],
children: [
%{
key: :host,
type: :string,
description: "Host",
suggestions: [
"example.com"
]
},
%{
key: :port,
type: :integer,
description: "port",
suggestions: [
2020
]
},
%{
key: :scheme,
type: :string,
description: "Scheme",
suggestions: [
"https",
"https"
]
}
]
},
%{
key: :instrumenters,
type: {:list, :module},
suggestions: [Pleroma.Web.Endpoint.Instrumenter]
},
%{
key: :protocol,
type: :string,
suggestions: ["https"]
},
%{
key: :secret_key_base,
type: :string,
suggestions: ["aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl"]
},
%{
key: :signing_salt,
type: :string,
suggestions: ["CqaoopA2"]
},
%{
key: :render_errors,
type: :keyword,
suggestions: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
children: [
%{
key: :view,
type: :module,
suggestions: [Pleroma.Web.ErrorView]
},
%{
key: :accepts,
type: {:list, :string},
suggestions: ["json"]
}
]
},
%{
key: :pubsub,
type: :keyword,
suggestions: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2],
children: [
%{
key: :name,
type: :module,
suggestions: [Pleroma.PubSub]
},
%{
key: :adapter,
type: :module,
suggestions: [Phoenix.PubSub.PG2]
}
]
},
%{
key: :secure_cookie_flag,
type: :boolean
},
%{
key: :extra_cookie_attrs,
type: {:list, :string},
suggestions: ["SameSite=Lax"]
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :activitypub, key: :activitypub,
@ -1805,6 +1651,12 @@
type: :boolean, type: :boolean,
description: "Sign object fetches with HTTP signatures" description: "Sign object fetches with HTTP signatures"
}, },
%{
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)."
},
%{ %{
key: :follow_handshake_timeout, key: :follow_handshake_timeout,
type: :integer, type: :integer,
@ -1871,9 +1723,8 @@
type: :string, type: :string,
description: description:
"A mailto link for the administrative contact." <> "A mailto link for the administrative contact." <>
" It's best if this email is not a personal email address, but rather a group email so that if a person leaves an organization," <> " It's best if this email is not a personal email address, but rather a group email to the instance moderation team.",
" is unavailable for an extended period, or otherwise can't respond, someone else on the list can.", suggestions: ["mailto:moderators@pleroma.com"]
suggestions: ["Subject"]
}, },
%{ %{
key: :public_key, key: :public_key,
@ -1940,7 +1791,7 @@
key: :admin_token, key: :admin_token,
type: :string, type: :string,
description: "Token", description: "Token",
suggestions: ["some_random_token"] suggestions: ["We recommend a secure random string or UUID"]
} }
] ]
}, },
@ -2002,6 +1853,7 @@
"Background jobs queues (keys: queues, values: max numbers of concurrent jobs)", "Background jobs queues (keys: queues, values: max numbers of concurrent jobs)",
suggestions: [ suggestions: [
activity_expiration: 10, activity_expiration: 10,
attachments_cleanup: 5,
background: 5, background: 5,
federator_incoming: 50, federator_incoming: 50,
federator_outgoing: 50, federator_outgoing: 50,
@ -2017,6 +1869,12 @@
description: "Activity expiration queue", description: "Activity expiration queue",
suggestions: [10] suggestions: [10]
}, },
%{
key: :attachments_cleanup,
type: :integer,
description: "Attachment deletion queue",
suggestions: [5]
},
%{ %{
key: :background, key: :background,
type: :integer, type: :integer,
@ -2060,6 +1918,18 @@
suggestions: [50] suggestions: [50]
} }
] ]
},
%{
key: :crontab,
type: {:list, :tuple},
description: "Settings for cron background jobs",
suggestions: [
{"0 0 * * *", Pleroma.Workers.Cron.ClearOauthTokenWorker},
{"0 * * * *", Pleroma.Workers.Cron.StatsWorker},
{"* * * * *", Pleroma.Workers.Cron.PurgeExpiredActivitiesWorker},
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
]
} }
] ]
}, },
@ -2101,7 +1971,7 @@
key: :unfurl_nsfw, key: :unfurl_nsfw,
label: "Unfurl NSFW", label: "Unfurl NSFW",
type: :boolean, type: :boolean,
description: "If set to `true` NSFW attachments will be shown in previews" description: "When enabled NSFW attachments will be shown in previews"
} }
] ]
}, },
@ -2115,7 +1985,7 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "Enables/disables RichMedia." description: "Enables RichMedia parsing of URLs."
}, },
%{ %{
key: :ignore_hosts, key: :ignore_hosts,
@ -2161,8 +2031,7 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: description: "Fetch posts when a new user is federated with"
"If enabled, when a new user is federated with, fetch some of their latest posts"
}, },
%{ %{
key: :pages, key: :pages,
@ -2181,13 +2050,13 @@
%{ %{
key: :class, key: :class,
type: [:string, false], type: [:string, false],
description: "Specify the class to be added to the generated link. `False` to clear", description: "Specify the class to be added to the generated link. Disable to clear",
suggestions: ["auto-linker", false] suggestions: ["auto-linker", false]
}, },
%{ %{
key: :rel, key: :rel,
type: [:string, false], type: [:string, false],
description: "Override the rel attribute. `False` to clear", description: "Override the rel attribute. Disable to clear",
suggestions: ["ugc", "noopener noreferrer", false] suggestions: ["ugc", "noopener noreferrer", false]
}, },
%{ %{
@ -2297,7 +2166,7 @@
key: :ssl, key: :ssl,
label: "SSL", label: "SSL",
type: :boolean, type: :boolean,
description: "`True` to use SSL, usually implies the port 636" description: "Enable to use SSL, usually implies the port 636"
}, },
%{ %{
key: :sslopts, key: :sslopts,
@ -2324,7 +2193,7 @@
key: :tls, key: :tls,
label: "TLS", label: "TLS",
type: :boolean, type: :boolean,
description: "`True` to start TLS, usually implies the port 389" description: "Enable to use STARTTLS, usually implies the port 389"
}, },
%{ %{
key: :tlsopts, key: :tlsopts,
@ -2373,8 +2242,8 @@
type: :boolean, type: :boolean,
description: description:
"OAuth admin scope requirement toggle. " <> "OAuth admin scope requirement toggle. " <>
"If `true`, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <> "If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
"(client app must support admin scopes). If `false` 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." "`is_admin` user flag grants access to admin-specific actions."
}, },
%{ %{
@ -2396,7 +2265,7 @@
key: :oauth_consumer_strategies, key: :oauth_consumer_strategies,
type: {:list, :string}, type: {:list, :string},
description: description:
"The list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <> "The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
" Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\"" <> " Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\"" <>
" (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_<strategy>).", " (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_<strategy>).",
suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"] suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"]
@ -2512,6 +2381,20 @@
} }
] ]
}, },
%{
group: :pleroma,
key: Pleroma.Emails.NewUsersDigestEmail,
type: :group,
description: "New users admin email digest",
children: [
%{
key: :enabled,
type: :boolean,
description: "enables new users admin digest email when `true`",
suggestions: [false]
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :oauth2, key: :oauth2,
@ -2533,7 +2416,7 @@
%{ %{
key: :clean_expired_tokens, key: :clean_expired_tokens,
type: :boolean, type: :boolean,
description: "Enable a background job to clean expired oauth tokens. Default: `false`." description: "Enable a background job to clean expired oauth tokens. Default: disabled."
} }
] ]
}, },
@ -2584,19 +2467,6 @@
} }
] ]
}, },
%{
group: :pleroma,
key: :database,
type: :group,
description: "Database related settings",
children: [
%{
key: :rum_enabled,
type: :boolean,
description: "If RUM indexes should be used. Default: `false`"
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :rate_limit, key: :rate_limit,
@ -2610,6 +2480,12 @@
description: "For the search requests (account & status search etc.)", description: "For the search requests (account & status search etc.)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
}, },
%{
key: :timeline,
type: [:tuple, {:list, :tuple}],
description: "For requests to timelines (each timeline has it's own limiter)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{ %{
key: :app_account_creation, key: :app_account_creation,
type: [:tuple, {:list, :tuple}], type: [:tuple, {:list, :tuple}],
@ -2760,20 +2636,6 @@
} }
] ]
}, },
%{
group: :prometheus,
key: Pleroma.Web.Endpoint.MetricsExporter,
type: :group,
description: "Prometheus settings",
children: [
%{
key: :path,
type: :string,
description: "API endpoint with metrics",
suggestions: ["/api/pleroma/app_metrics"]
}
]
},
%{ %{
group: :http_signatures, group: :http_signatures,
type: :group, type: :group,
@ -2979,7 +2841,7 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "Enable/disable the plug. Default: `false`." description: "Enable/disable the plug. Default: disabled."
}, },
%{ %{
key: :headers, key: :headers,
@ -3033,7 +2895,7 @@
%{ %{
key: :enabled, key: :enabled,
type: :boolean, type: :boolean,
description: "Enables the rendering of static HTML. Defaults to `false`." description: "Enables the rendering of static HTML. Default: disabled."
} }
] ]
}, },
@ -3041,7 +2903,7 @@
group: :pleroma, group: :pleroma,
key: :feed, key: :feed,
type: :group, type: :group,
description: "Configure feed rendering.", description: "Configure feed rendering",
children: [ children: [
%{ %{
key: :post_title, key: :post_title,
@ -3091,7 +2953,7 @@
group: :pleroma, group: :pleroma,
key: :modules, key: :modules,
type: :group, type: :group,
description: "Custom Runtime Modules.", description: "Custom Runtime Modules",
children: [ children: [
%{ %{
key: :runtime_dir, key: :runtime_dir,
@ -3102,14 +2964,21 @@
}, },
%{ %{
group: :pleroma, group: :pleroma,
key: :streamer,
type: :group, type: :group,
description: "Allow instance configuration from database.", description: "Settings for notifications streamer",
children: [ children: [
%{ %{
key: :configurable_from_database, key: :workers,
type: :boolean, type: :integer,
description: description: "Number of workers to send notifications.",
"Allow transferring configuration to DB with the subsequent customization from Admin api. Defaults to `false`" suggestions: [3]
},
%{
key: :overflow_workers,
type: :integer,
description: "Maximum number of workers created if pool is empty.",
suggestions: [2]
} }
] ]
} }

View file

@ -74,11 +74,7 @@
total_user_limit: 3, total_user_limit: 3,
enabled: false enabled: false
config :pleroma, :rate_limit, config :pleroma, :rate_limit, %{}
search: [{1000, 30}, {1000, 30}],
app_account_creation: {10_000, 5},
password_reset: {1000, 30},
ap_routes: nil
config :pleroma, :http_security, report_uri: "https://endpoint.com" config :pleroma, :http_security, report_uri: "https://endpoint.com"
@ -94,6 +90,8 @@
config :pleroma, :modules, runtime_dir: "test/fixtures/modules" config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View file

@ -260,10 +260,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- `nickname` or `id` - `nickname` or `id`
- *optional* `page_size`: number of statuses to return (default is `20`) - *optional* `page_size`: number of statuses to return (default is `20`)
- *optional* `godmode`: `true`/`false` allows to see private statuses - *optional* `godmode`: `true`/`false` allows to see private statuses
- *optional* `with_reblogs`: `true`/`false` allows to see reblogs (default is false)
- Response: - Response:
- On failure: `Not found` - On failure: `Not found`
- On success: JSON array of user's latest statuses - On success: JSON array of user's latest statuses
## `GET /api/pleroma/admin/instances/:instance/statuses`
### Retrive instance's latest statuses
- Params:
- `instance`: instance name
- *optional* `page_size`: number of statuses to return (default is `20`)
- *optional* `godmode`: `true`/`false` allows to see private statuses
- *optional* `with_reblogs`: `true`/`false` allows to see reblogs (default is false)
- Response:
- On failure: `Not found`
- On success: JSON array of instance's latest statuses
## `POST /api/pleroma/admin/relay` ## `POST /api/pleroma/admin/relay`
### Follow a Relay ### Follow a Relay
@ -682,6 +696,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
### Get list of merged default settings with saved in database. ### Get list of merged default settings with saved in database.
*If `need_reboot` flag exists in response, instance must be restarted, so reboot time settings can take effect.*
**Only works when configuration from database is enabled.** **Only works when configuration from database is enabled.**
- Params: - Params:
@ -692,20 +708,24 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
```json ```json
{ {
configs: [ "configs": [
{ {
"group": ":pleroma", "group": ":pleroma",
"key": "Pleroma.Upload", "key": "Pleroma.Upload",
"value": [] "value": []
} }
] ],
"need_reboot": true
} }
``` ```
need_reboot - *optional*, if were changed reboot time settings.
## `POST /api/pleroma/admin/config` ## `POST /api/pleroma/admin/config`
### Update config settings ### Update config settings
*If `need_reboot` flag exists in response, instance must be restarted, so reboot time settings can take effect.*
**Only works when configuration from database is enabled.** **Only works when configuration from database is enabled.**
Some modifications are necessary to save the config settings correctly: Some modifications are necessary to save the config settings correctly:
@ -793,7 +813,7 @@ config :quack,
``` ```
```json ```json
{ {
configs: [ "configs": [
{"group": ":quack", "key": ":level", "value": ":debug"}, {"group": ":quack", "key": ":level", "value": ":debug"},
{"group": ":quack", "key": ":meta", "value": [":all"]}, {"group": ":quack", "key": ":meta", "value": [":all"]},
... ...
@ -804,7 +824,7 @@ config :quack,
```json ```json
{ {
configs: [ "configs": [
{ {
"group": ":pleroma", "group": ":pleroma",
"key": "Pleroma.Upload", "key": "Pleroma.Upload",
@ -836,15 +856,17 @@ config :quack,
- 400 Bad Request `"To use this endpoint you need to enable configuration from database."` - 400 Bad Request `"To use this endpoint you need to enable configuration from database."`
```json ```json
{ {
configs: [ "configs": [
{ {
"group": ":pleroma", "group": ":pleroma",
"key": "Pleroma.Upload", "key": "Pleroma.Upload",
"value": [...] "value": [...]
} }
] ],
"need_reboot": true
} }
``` ```
need_reboot - *optional*, if were changed reboot time settings.
## ` GET /api/pleroma/admin/config/descriptions` ## ` GET /api/pleroma/admin/config/descriptions`
@ -931,3 +953,20 @@ Loads json generated from `config/descriptions.exs`.
- Params: - Params:
- `nicknames` - `nicknames`
- Response: Array of user nicknames - Response: Array of user nicknames
## `GET /api/pleroma/admin/stats`
### Stats
- Response:
```json
{
"status_visibility": {
"direct": 739,
"private": 9,
"public": 17,
"unlisted": 14
}
}
```

View file

@ -459,3 +459,16 @@ Emoji reactions work a lot like favourites do. They make it possible to react to
{"name": "☕", "count": 1, "me": false, "accounts": [{"id" => "abc..."}]} {"name": "☕", "count": 1, "me": false, "accounts": [{"id" => "abc..."}]}
] ]
``` ```
## `GET /api/v1/pleroma/statuses/:id/reactions/:emoji`
### Get an object of emoji to account mappings with accounts that reacted to the post for a specific emoji`
* Method: `GET`
* Authentication: optional
* Params: None
* Response: JSON, a list of emoji/account list tuples
* Example Response:
```json
[
{"name": "😀", "count": 2, "me": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}
]
```

View file

@ -18,7 +18,9 @@
6. Run `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>` 6. Run `sudo -Hu postgres pg_restore -d <pleroma_db> -v -1 </path/to/backup_location/pleroma.pgdump>`
7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any. 7. If you installed a newer Pleroma version, you should run `mix ecto.migrate`[^1]. This task performs database migrations, if there were any.
8. Restart the Pleroma service. 8. Restart the Pleroma service.
9. After you've restarted Pleroma, you will notice that postgres will take up more cpu resources than usual. A lot in fact. To fix this you must do a VACUUM ANLAYZE. This can also be done while the instance is still running like so:
$ sudo -u postgres psql pleroma_database_name
pleroma=# VACUUM ANALYZE;
[^1]: Prefix with `MIX_ENV=prod` to run it using the production config file. [^1]: Prefix with `MIX_ENV=prod` to run it using the production config file.
## Remove ## Remove

View file

@ -1,4 +1,21 @@
# Updating your instance # Updating your instance
You should **always check the release notes/changelog** in case there are config deprecations, special update special update steps, etc.
Besides that, doing the following is generally enough:
## For OTP installations
```sh
# Download the new release
su pleroma -s $SHELL -lc "./bin/pleroma_ctl update"
# Migrate the database, you are advised to stop the instance before doing that
su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
```
## For from source installations (using git)
1. Go to the working directory of Pleroma (default is `/opt/pleroma`) 1. Go to the working directory of Pleroma (default is `/opt/pleroma`)
2. Run `git pull`. This pulls the latest changes from upstream. 2. Run `git pull`. This pulls the latest changes from upstream.
3. Run `mix deps.get`. This pulls in any new dependencies. 3. Run `mix deps.get`. This pulls in any new dependencies.

View file

@ -2,9 +2,11 @@
This is a cheat sheet for Pleroma configuration file, any setting possible to configure should be listed here. This is a cheat sheet for Pleroma configuration file, any setting possible to configure should be listed here.
Pleroma configuration works by first importing the base config (`config/config.exs` on source installs, compiled-in on OTP releases), then overriding it by the environment config (`config/$MIX_ENV.exs` on source installs, N/A to OTP releases) and then overriding it by user config (`config/$MIX_ENV.secret.exs` on source installs, typically `/etc/pleroma/config.exs` on OTP releases). For OTP installations the configuration is typically stored in `/etc/pleroma/config.exs`.
You shouldn't edit the base config directly to avoid breakages and merge conflicts, but it can be used as a reference if you don't understand how an option is supposed to be formatted, the latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). For from source installations Pleroma configuration works by first importing the base config `config/config.exs`, then overriding it by the environment config `config/$MIX_ENV.exs` and then overriding it by user config `config/$MIX_ENV.secret.exs`. In from source installations you should always make the changes to the user config and NEVER to the base config to avoid breakages and merge conflicts. So for production you change/add configuration to `config/prod.secret.exs`.
To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted.
## :instance ## :instance
* `name`: The instances name. * `name`: The instances name.
@ -148,14 +150,19 @@ config :pleroma, :mrf_user_allowlist,
* `days`: Default global expiration time for all local Create activities (in days) * `days`: Default global expiration time for all local Create activities (in days)
### :activitypub ### :activitypub
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed * `unfollow_blocked`: Whether blocks result in people getting unfollowed
* ``outgoing_blocks``: Whether to federate blocks to other instances * `outgoing_blocks`: Whether to federate blocks to other instances
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question * `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question
* ``sign_object_fetches``: Sign object fetches with HTTP signatures * `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches
### :fetch_initial_posts ### :fetch_initial_posts
* `enabled`: if enabled, when a new user is federated with, fetch some of their latest posts
* `pages`: the amount of pages to fetch !!! warning
Be careful with this setting, fetching posts may lead to new users being discovered whose posts will then also be fetched. This can lead to serious load on your instance and database.
* `enabled`: If enabled, when a new user is discovered by your instance, fetch some of their latest posts.
* `pages`: The amount of pages to fetch
## Pleroma.ScheduledActivity ## Pleroma.ScheduledActivity
@ -347,6 +354,7 @@ Means that:
Supported rate limiters: Supported rate limiters:
* `:search` - Account/Status search. * `:search` - Account/Status search.
* `:timeline` - Timeline requests (each timeline has it's own limiter).
* `:app_account_creation` - Account registration from the API. * `:app_account_creation` - Account registration from the API.
* `:relations_actions` - Following/Unfollowing in general. * `:relations_actions` - Following/Unfollowing in general.
* `:relation_id_action` - Following/Unfollowing for a specific user. * `:relation_id_action` - Following/Unfollowing for a specific user.
@ -506,6 +514,10 @@ Email notifications settings.
- `:logo` - a path to a custom logo. Set it to `nil` to use the default Pleroma logo. - `:logo` - a path to a custom logo. Set it to `nil` to use the default Pleroma logo.
- `:styling` - a map with color settings for email templates. - `:styling` - a map with color settings for email templates.
### Pleroma.Emails.NewUsersDigestEmail
- `:enabled` - a boolean, enables new users admin digest email when `true`. Defaults to `false`.
## Background jobs ## Background jobs
### Oban ### Oban

View file

@ -0,0 +1,74 @@
# Theming your instance
To add a custom theme to your instance, you'll first need to get a custom theme, upload it to the server, make it available to the instance and eventually you can set it as default.
## Getting a custom theme
### Create your own theme
* You can create your own theme using the Pleroma FE by going to settings (gear on the top right) and choose the Theme tab. Here you have the options to create a personal theme.
* To download your theme, you can do Save preset
* If you want to upload a theme to customise it further, you can upload it using Load preset
This will only save the theme for you personally. To make it available to the whole instance, you'll need to upload it to the server.
### Get an existing theme
* You can download a theme from another instance by going to that instance, go to settings and make sure you have the theme selected that you want. Then you can do Save preset to download it.
* You can also find and download custom themes at <https://plthemes.vulpes.one/>
## Adding the custom theme to the instance
### Upload the theme to the server
Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `priv/static/static/styles.json`.
Example of `styles.json` where we add our own `my-awesome-theme.json`
```json
{
"pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ],
"bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"],
"ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ],
"monokai": [ "Monokai", "#272822", "#383830", "#f8f8f2", "#f92672", "#F92672", "#a6e22e", "#66d9ef", "#f4bf75" ],
"redmond-xx": "/static/themes/redmond-xx.json",
"redmond-xx-se": "/static/themes/redmond-xx-se.json",
"redmond-xxi": "/static/themes/redmond-xxi.json",
"breezy-dark": "/static/themes/breezy-dark.json",
"breezy-light": "/static/themes/breezy-light.json",
"mammal": "/static/themes/mammal.json",
"my-awesome-theme": "/static/themes/my-awesome-theme.json"
}
```
Now you'll already be able to select the theme in Pleroma FE from the drop-down. You don't need to restart Pleroma because we only changed static served files. You may need to refresh the page in your browser. You'll notice however that the theme doesn't have a name, it's just an empty entry in the drop-down.
### Give the theme a name
When you open one of the themes that ship with Pleroma, you'll notice that the json has a `"name"` key. Add a key-value pair to your theme where the key name is `"name"` and the value the name you want to give your theme. After this you can refresh te page in your browser and the name should be visible in the drop-down.
Example of `my-awesome-theme.json` where we add the name "My Awesome Theme"
```json
{
"_pleroma_theme_version": 2,
"name": "My Awesome Theme",
"theme": {}
}
```
### Set as default theme
Now we can set the new theme as default in the [Pleroma FE configuration](General-tips-for-customizing-Pleroma-FE.md).
Example of adding the new theme in the back-end config files
```elixir
config :pleroma, :frontend_configurations,
pleroma_fe: %{
theme: "my-awesome-theme"
}
```
If you added it in the back-end configuration file, you'll need to restart your instance for the changes to take effect. If you don't see the changes, it's probably because the browser has cached the previous theme. In that case you'll want to clear browser caches. Alternatively you can use a private/incognito window just to see the changes.

View file

@ -123,7 +123,7 @@ In addition to that, replace the existing nginx config's contents with the examp
If not an I2P-only instance, add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`. If not an I2P-only instance, add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`.
And for both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself seperately from the clearnet (if your instance is also on the clearnet). And for both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself separately from the clearnet (if your instance is also on the clearnet).
Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/): Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/):
``` ```
config :pleroma, :http_security, config :pleroma, :http_security,

View file

@ -1,4 +1,5 @@
# Message Rewrite Facility # Message Rewrite Facility
The Message Rewrite Facility (MRF) is a subsystem that is implemented as a series of hooks that allows the administrator to rewrite or discard messages. The Message Rewrite Facility (MRF) is a subsystem that is implemented as a series of hooks that allows the administrator to rewrite or discard messages.
Possible uses include: Possible uses include:
@ -10,7 +11,8 @@ Possible uses include:
* removing media from messages * removing media from messages
* sending only public messages to a specific instance * sending only public messages to a specific instance
The MRF provides user-configurable policies. The default policy is `NoOpPolicy`, which disables the MRF functionality. Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module. The MRF provides user-configurable policies. The default policy is `NoOpPolicy`, which disables the MRF functionality. Pleroma also includes an easy to use policy called `SimplePolicy` which maps messages matching certain pre-defined criterion to actions built into the policy module.
It is possible to use multiple, active MRF policies at the same time. It is possible to use multiple, active MRF policies at the same time.
## Quarantine Instances ## Quarantine Instances
@ -18,7 +20,8 @@ It is possible to use multiple, active MRF policies at the same time.
You have the ability to prevent from private / followers-only messages from federating with specific instances. Which means they will only get the public or unlisted messages from your instance. You have the ability to prevent from private / followers-only messages from federating with specific instances. Which means they will only get the public or unlisted messages from your instance.
If, for example, you're using `MIX_ENV=prod` aka using production mode, you would open your configuration file located in `config/prod.secret.exs` and edit or add the option under your `:instance` config object. Then you would specify the instance within quotes. If, for example, you're using `MIX_ENV=prod` aka using production mode, you would open your configuration file located in `config/prod.secret.exs` and edit or add the option under your `:instance` config object. Then you would specify the instance within quotes.
```
```elixir
config :pleroma, :instance, config :pleroma, :instance,
[...] [...]
quarantined_instances: ["instance.example", "other.example"] quarantined_instances: ["instance.example", "other.example"]
@ -28,15 +31,15 @@ config :pleroma, :instance,
`SimplePolicy` is capable of handling most common admin tasks. `SimplePolicy` is capable of handling most common admin tasks.
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this: To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
``` ```elixir
config :pleroma, :instance, config :pleroma, :instance,
[...] [...]
rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy
``` ```
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are: Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
* `media_removal`: Servers in this group will have media stripped from incoming messages. * `media_removal`: Servers in this group will have media stripped from incoming messages.
* `media_nsfw`: Servers in this group will have the #nsfw tag and sensitive setting injected into incoming messages which contain media. * `media_nsfw`: Servers in this group will have the #nsfw tag and sensitive setting injected into incoming messages which contain media.
@ -50,7 +53,7 @@ Servers should be configured as lists.
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`: This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
``` ```elixir
config :pleroma, :instance, config :pleroma, :instance,
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy] rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
@ -60,30 +63,31 @@ config :pleroma, :mrf_simple,
reject: ["spam.com"], reject: ["spam.com"],
federated_timeline_removal: ["spam.university"], federated_timeline_removal: ["spam.university"],
report_removal: ["whiny.whiner"] report_removal: ["whiny.whiner"]
``` ```
### Use with Care ### Use with Care
The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance. The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance.
## Writing your own MRF Policy ## Writing your own MRF Policy
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting. As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
For example, here is a sample policy module which rewrites all messages to "new message content": For example, here is a sample policy module which rewrites all messages to "new message content":
```elixir ```elixir
# This is a sample MRF policy which rewrites all Notes to have "new message defmodule Pleroma.Web.ActivityPub.MRF.RewritePolicy do
# content." @moduledoc "MRF policy which rewrites all Notes to have 'new message content'."
defmodule Site.RewritePolicy do @behaviour Pleroma.Web.ActivityPub.MRF
@behavior Pleroma.Web.ActivityPub.MRF
# Catch messages which contain Note objects with actual data to filter. # Catch messages which contain Note objects with actual data to filter.
# Capture the object as `object`, the message content as `content` and the # Capture the object as `object`, the message content as `content` and the
# message itself as `message`. # message itself as `message`.
@impl true @impl true
def filter(%{"type" => Create", "object" => {"type" => "Note", "content" => content} = object} = message) def filter(
%{"type" => "Create", "object" => %{"type" => "Note", "content" => content} = object} =
message
)
when is_binary(content) do when is_binary(content) do
# Subject / CW is stored as summary instead of `name` like other AS2 objects # Subject / CW is stored as summary instead of `name` like other AS2 objects
# because of Mastodon doing it that way. # because of Mastodon doing it that way.
@ -106,17 +110,22 @@ defmodule Site.RewritePolicy do
# Let all other messages through without modifying them. # Let all other messages through without modifying them.
@impl true @impl true
def filter(message), do: {:ok, message} def filter(message), do: {:ok, message}
@impl true
def describe do
{:ok, %{mrf_sample: %{content: "new message content"}}}`
end
end end
``` ```
If you save this file as `lib/site/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so: If you save this file as `lib/pleroma/web/activity_pub/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
``` ```elixir
config :pleroma, :instance, config :pleroma, :instance,
rewrite_policy: [ rewrite_policy: [
Pleroma.Web.ActivityPub.MRF.SimplePolicy, Pleroma.Web.ActivityPub.MRF.SimplePolicy,
Site.RewritePolicy Pleroma.Web.ActivityPub.MRF.RewritePolicy
] ]
``` ```
Please note that the Pleroma developers consider custom MRF policy modules to fall under the purview of the AGPL. As such, you are obligated to release the sources to your custom MRF policy modules upon request. Please note that the Pleroma developers consider custom MRF policy modules to fall under the purview of the AGPL. As such, you are obligated to release the sources to your custom MRF policy modules upon request.

View file

@ -75,7 +75,7 @@ If not a Tor-only instance,
add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`. add the nginx config below to your existing config at `/etc/nginx/sites-enabled/pleroma.nginx`.
--- ---
For both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself seperately from the clearnet (if your instance is also on the clearnet). For both cases, disable CSP in Pleroma's config (STS is disabled by default) so you can define those yourself separately from the clearnet (if your instance is also on the clearnet).
Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/): Copy the following into the `config/prod.secret.exs` in your Pleroma folder (/home/pleroma/pleroma/):
``` ```
config :pleroma, :http_security, config :pleroma, :http_security,

View file

@ -73,6 +73,15 @@ rc-service postgresql restart
systemctl restart postgresql systemctl restart postgresql
``` ```
If you are using PostgreSQL 12 or higher, add this to your Ecto database configuration
```elixir
prepare: :named,
parameters: [
plan_cache_mode: "force_custom_plan"
]
```
### Installing Pleroma ### Installing Pleroma
```sh ```sh
# Create a Pleroma user # Create a Pleroma user
@ -259,19 +268,14 @@ su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --a
``` ```
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password. This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
### Updating
Generally, doing the following is enough:
```sh
# Download the new release
su pleroma -s $SHELL -lc "./bin/pleroma_ctl update"
# Migrate the database, you are advised to stop the instance before doing that
su pleroma -s $SHELL -lc "./bin/pleroma_ctl migrate"
```
But you should **always check the release notes/changelog** in case there are config deprecations, special update steps, etc.
## Further reading ## Further reading
* [Backup your instance](../administration/backup.md) * [Backup your instance](../administration/backup.md)
* [Hardening your instance](../configuration/hardening.md) * [Hardening your instance](../configuration/hardening.md)
* [How to activate mediaproxy](../configuration/howto_mediaproxy.md) * [How to activate mediaproxy](../configuration/howto_mediaproxy.md)
* [Updating your instance](../administration/updating.md)
## Questions
Questions about the installation or didnt it work as it should be, ask in [#pleroma:matrix.org](https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org) or IRC Channel **#pleroma** on **Freenode**.

View file

@ -41,7 +41,7 @@ On the top right you will also see a wrench icon. This opens your personal setti
This is where the interesting stuff happens! This is where the interesting stuff happens!
Depending on the timeline you will see different statuses, but each status has a standard structure: Depending on the timeline you will see different statuses, but each status has a standard structure:
- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). Clicking on the profile pic will uncollapse the user's profile. - Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the reply-to status). Clicking on the profile pic will uncollapse the user's profile.
- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime! - A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime!
- An arrow icon allows you to open the status on the instance where it's originating from. - An arrow icon allows you to open the status on the instance where it's originating from.
- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person. - The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person.

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Config do defmodule Mix.Tasks.Pleroma.Config do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Emoji do defmodule Mix.Tasks.Pleroma.Emoji do
@ -186,11 +186,7 @@ def run(["gen-pack", src]) do
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}") tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
{:ok, _} = {:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
:zip.unzip(
binary_archive,
cwd: tmp_pack_dir
)
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts) emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)

View file

@ -1,11 +1,13 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Instance do defmodule Mix.Tasks.Pleroma.Instance do
use Mix.Task use Mix.Task
import Mix.Pleroma import Mix.Pleroma
alias Pleroma.Config
@shortdoc "Manages Pleroma instance" @shortdoc "Manages Pleroma instance"
@moduledoc File.read!("docs/administration/CLI_tasks/instance.md") @moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
@ -63,7 +65,8 @@ def run(["gen" | rest]) do
get_option( get_option(
options, options,
:instance_name, :instance_name,
"What is the name of your instance? (e.g. Pleroma/Soykaf)" "What is the name of your instance? (e.g. The Corndog Emporium)",
domain
) )
email = get_option(options, :admin_email, "What is your admin email address?") email = get_option(options, :admin_email, "What is your admin email address?")
@ -153,6 +156,8 @@ def run(["gen" | rest]) do
Pleroma.Config.get([:instance, :static_dir]) Pleroma.Config.get([:instance, :static_dir])
) )
Config.put([:instance, :static_dir], static_dir)
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8) signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
@ -202,8 +207,14 @@ def run(["gen" | rest]) do
write_robots_txt(indexable, template_dir) write_robots_txt(indexable, template_dir)
shell_info( shell_info(
"\n All files successfully written! Refer to the installation instructions for your platform for next steps" "\n All files successfully written! Refer to the installation instructions for your platform for next steps."
) )
if db_configurable? do
shell_info(
" Please transfer your config to the database after running database migrations. Refer to \"Transfering the config to/from the database\" section of the docs for more information."
)
end
else else
shell_error( shell_error(
"The task would have overwritten the following files:\n" <> "The task would have overwritten the following files:\n" <>

View file

@ -0,0 +1,46 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
@shortdoc "Refreshes counter cache"
use Mix.Task
alias Pleroma.Activity
alias Pleroma.CounterCache
alias Pleroma.Repo
require Logger
import Ecto.Query
def run([]) do
Mix.Pleroma.start_pleroma()
["public", "unlisted", "private", "direct"]
|> Enum.each(fn visibility ->
count = status_visibility_count_query(visibility)
name = "status_visibility_#{visibility}"
CounterCache.set(name, count)
Mix.Pleroma.shell_info("Set #{name} to #{count}")
end)
Mix.Pleroma.shell_info("Done")
end
defp status_visibility_count_query(visibility) do
Activity
|> where(
[a],
fragment(
"activity_visibility(?, ?, ?) = ?",
a.actor,
a.recipients,
a.data,
^visibility
)
)
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
end
end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.RobotsTxt do defmodule Mix.Tasks.Pleroma.RobotsTxt do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.User do defmodule Mix.Tasks.Pleroma.User do
@ -100,8 +100,7 @@ def run(["rm", nickname]) do
User.perform(:delete, user) User.perform(:delete, user)
shell_info("User #{nickname} deleted.") shell_info("User #{nickname} deleted.")
else else
_ -> _ -> shell_error("No local user #{nickname}")
shell_error("No local user #{nickname}")
end end
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity do defmodule Pleroma.Activity do
@ -310,7 +310,7 @@ def follow_requests_for_actor(%Pleroma.User{ap_id: ap_id}) do
def restrict_deactivated_users(query) do def restrict_deactivated_users(query) do
deactivated_users = deactivated_users =
from(u in User.Query.build(deactivated: true), select: u.ap_id) from(u in User.Query.build(%{deactivated: true}), select: u.ap_id)
|> Repo.all() |> Repo.all()
Activity.Queries.exclude_authors(query, deactivated_users) Activity.Queries.exclude_authors(query, deactivated_users)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity.Queries do defmodule Pleroma.Activity.Queries do
@ -7,7 +7,7 @@ defmodule Pleroma.Activity.Queries do
Contains queries for Activity. Contains queries for Activity.
""" """
import Ecto.Query, only: [from: 2] import Ecto.Query, only: [from: 2, where: 3]
@type query :: Ecto.Queryable.t() | Activity.t() @type query :: Ecto.Queryable.t() | Activity.t()
@ -30,7 +30,7 @@ def by_actor(query \\ Activity, actor) do
) )
end end
@spec by_author(query, String.t()) :: query @spec by_author(query, User.t()) :: query
def by_author(query \\ Activity, %User{ap_id: ap_id}) do def by_author(query \\ Activity, %User{ap_id: ap_id}) do
from(a in query, where: a.actor == ^ap_id) from(a in query, where: a.actor == ^ap_id)
end end
@ -63,6 +63,22 @@ def by_object_id(query, object_id) when is_binary(object_id) do
) )
end end
@spec by_object_in_reply_to_id(query, String.t(), keyword()) :: query
def by_object_in_reply_to_id(query, in_reply_to_id, opts \\ []) do
query =
if opts[:skip_preloading] do
Activity.with_joined_object(query)
else
Activity.with_preloaded_object(query)
end
where(
query,
[activity, object: o],
fragment("(?)->>'inReplyTo' = ?", o.data, ^to_string(in_reply_to_id))
)
end
@spec by_type(query, String.t()) :: query @spec by_type(query, String.t()) :: query
def by_type(query \\ Activity, activity_type) do def by_type(query \\ Activity, activity_type) do
from( from(

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Activity.Search do defmodule Pleroma.Activity.Search do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ActivityExpiration do defmodule Pleroma.ActivityExpiration do
@ -62,6 +62,6 @@ def validate_scheduled_at(changeset) do
def expires_late_enough?(scheduled_at) do def expires_late_enough?(scheduled_at) do
now = NaiveDateTime.utc_now() now = NaiveDateTime.utc_now()
diff = NaiveDateTime.diff(scheduled_at, now, :millisecond) diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
diff >= @min_activity_lifetime diff > @min_activity_lifetime
end end
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Application do defmodule Pleroma.Application do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha do defmodule Pleroma.Captcha do
@ -50,7 +50,7 @@ def handle_call(:new, _from, state) do
token = new_captcha[:token] token = new_captcha[:token]
secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt")
sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign")
# Basicallty copy what Phoenix.Token does here, add the time to # Basically copy what Phoenix.Token does here, add the time to
# the actual data and make it a binary to then encrypt it # the actual data and make it a binary to then encrypt it
encrypted_captcha_answer = encrypted_captcha_answer =
%{ %{
@ -62,7 +62,7 @@ def handle_call(:new, _from, state) do
{ {
:reply, :reply,
# Repalce the answer with the encrypted answer # Replace the answer with the encrypted answer
%{new_captcha | answer_data: encrypted_captcha_answer}, %{new_captcha | answer_data: encrypted_captcha_answer},
state state
} }
@ -82,7 +82,8 @@ def handle_call({:validate, token, captcha, answer_data}, _from, state) do
valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid)
result = result =
with {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), with false <- is_nil(answer_data),
{:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret),
%{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do
try do try do
if DateTime.before?(at, valid_if_after), if DateTime.before?(at, valid_if_after),

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Captcha.Native do defmodule Pleroma.Captcha.Native do
@ -10,8 +10,8 @@ defmodule Pleroma.Captcha.Native do
@impl Service @impl Service
def new do def new do
case Captcha.get() do case Captcha.get() do
{:timeout} -> :error ->
%{error: dgettext("errors", "Captcha timeout")} %{error: dgettext("errors", "Captcha error")}
{:ok, answer_data, img_binary} -> {:ok, answer_data, img_binary} ->
%{ %{

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ConfigDB do defmodule Pleroma.ConfigDB do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Holder do defmodule Pleroma.Config.Holder do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Loader do defmodule Pleroma.Config.Loader do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.TransferTask do defmodule Pleroma.Config.TransferTask do
@ -146,9 +146,7 @@ defp group_and_subkey_need_reboot?(group, key, value) do
defp update_env(group, key, nil), do: Application.delete_env(group, key) defp update_env(group, key, nil), do: Application.delete_env(group, key)
defp update_env(group, key, value), do: Application.put_env(group, key, value) defp update_env(group, key, value), do: Application.put_env(group, key, value)
defp restart(_, :pleroma, :test), do: Logger.warn("pleroma restarted") defp restart(_, :pleroma, env), do: Restarter.Pleroma.restart_after_boot(env)
defp restart(_, :pleroma, _), do: send(Restarter.Pleroma, :after_boot)
defp restart(started_applications, app, _) do defp restart(started_applications, app, _) do
with {^app, _, _} <- List.keyfind(started_applications, app, 0), with {^app, _, _} <- List.keyfind(started_applications, app, 0),

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Conversation.Participation do defmodule Pleroma.Conversation.Participation do
@ -133,10 +133,8 @@ def restrict_recipients(query, user, %{"recipients" => user_ids}) do
[user.id | user_ids] [user.id | user_ids]
|> Enum.uniq() |> Enum.uniq()
|> Enum.reduce([], fn user_id, acc -> |> Enum.reduce([], fn user_id, acc ->
case FlakeId.Ecto.CompatType.dump(user_id) do {:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
{:ok, user_id} -> [user_id | acc] [user_id | acc]
_ -> acc
end
end) end)
conversation_subquery = conversation_subquery =

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.CounterCache do
alias Pleroma.CounterCache
alias Pleroma.Repo
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
schema "counter_cache" do
field(:name, :string)
field(:count, :integer)
end
def changeset(struct, params) do
struct
|> cast(params, [:name, :count])
|> validate_required([:name])
|> unique_constraint(:name)
end
def get_as_map(names) when is_list(names) do
CounterCache
|> where([cc], cc.name in ^names)
|> Repo.all()
|> Enum.group_by(& &1.name, & &1.count)
|> Map.new(fn {k, v} -> {k, hd(v)} end)
end
def set(name, count) do
%CounterCache{}
|> changeset(%{"name" => name, "count" => count})
|> Repo.insert(
on_conflict: [set: [count: count]],
returning: true,
conflict_target: :name
)
end
end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.AdminEmail do defmodule Pleroma.Emails.AdminEmail do

View file

@ -0,0 +1,32 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Emails.NewUsersDigestEmail do
use Phoenix.Swoosh, view: Pleroma.Web.EmailView, layout: {Pleroma.Web.LayoutView, :email_styled}
defp instance_notify_email do
Pleroma.Config.get([:instance, :notify_email]) || Pleroma.Config.get([:instance, :email])
end
def new_users(to, users_and_statuses) do
instance_name = Pleroma.Config.get([:instance, :name])
styling = Pleroma.Config.get([Pleroma.Emails.UserEmail, :styling])
logo_url =
Pleroma.Web.Endpoint.url() <>
Pleroma.Config.get([:frontend_configurations, :pleroma_fe, :logo])
new()
|> to({to.name, to.email})
|> from({instance_name, instance_notify_email()})
|> subject("#{instance_name} New Users")
|> render_body("new_users_digest.html", %{
title: "New Users",
users_and_statuses: users_and_statuses,
instance: instance_name,
styling: styling,
logo_url: logo_url
})
end
end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.FollowingRelationship do defmodule Pleroma.FollowingRelationship do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Formatter do defmodule Pleroma.Formatter do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTML do defmodule Pleroma.HTML do
@ -108,6 +108,7 @@ def extract_first_external_url(object, content) do
Cachex.fetch!(:scrubber_cache, key, fn _key -> Cachex.fetch!(:scrubber_cache, key, fn _key ->
result = result =
content content
|> Floki.parse_fragment!()
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]") |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|> Floki.attribute("a", "href") |> Floki.attribute("a", "href")
|> Enum.at(0) |> Enum.at(0)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MIME do defmodule Pleroma.MIME do
@ -9,7 +9,7 @@ defmodule Pleroma.MIME do
@default "application/octet-stream" @default "application/octet-stream"
@read_bytes 35 @read_bytes 35
@spec file_mime_type(String.t()) :: @spec file_mime_type(String.t(), String.t()) ::
{:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error {:ok, content_type :: String.t(), filename :: String.t()} | {:error, any()} | :error
def file_mime_type(path, filename) do def file_mime_type(path, filename) do
with {:ok, content_type} <- file_mime_type(path), with {:ok, content_type} <- file_mime_type(path),

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Notification do defmodule Pleroma.Notification do

View file

@ -1,10 +1,13 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object do defmodule Pleroma.Object do
use Ecto.Schema use Ecto.Schema
import Ecto.Query
import Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Fetcher alias Pleroma.Object.Fetcher
@ -12,9 +15,6 @@ defmodule Pleroma.Object do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
import Ecto.Query
import Ecto.Changeset
require Logger require Logger
@type t() :: %__MODULE__{} @type t() :: %__MODULE__{}
@ -145,18 +145,18 @@ def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}),
# Legacy objects can be mutated by anybody # Legacy objects can be mutated by anybody
def authorize_mutation(%Object{}, %User{}), do: true def authorize_mutation(%Object{}, %User{}), do: true
@spec get_cached_by_ap_id(String.t()) :: Object.t() | nil
def get_cached_by_ap_id(ap_id) do def get_cached_by_ap_id(ap_id) do
key = "object:#{ap_id}" key = "object:#{ap_id}"
Cachex.fetch!(:object_cache, key, fn _ -> with {:ok, nil} <- Cachex.get(:object_cache, key),
object = get_by_ap_id(ap_id) object when not is_nil(object) <- get_by_ap_id(ap_id),
{:ok, true} <- Cachex.put(:object_cache, key, object) do
if object do object
{:commit, object} else
else {:ok, object} -> object
{:ignore, object} nil -> nil
end end
end)
end end
def context_mapping(context) do def context_mapping(context) do
@ -301,4 +301,26 @@ def update_data(%Object{data: data} = object, attrs \\ %{}) do
def local?(%Object{data: %{"id" => id}}) do def local?(%Object{data: %{"id" => id}}) do
String.starts_with?(id, Pleroma.Web.base_url() <> "/") String.starts_with?(id, Pleroma.Web.base_url() <> "/")
end end
def replies(object, opts \\ []) do
object = Object.normalize(object)
query =
Object
|> where(
[o],
fragment("(?)->>'inReplyTo' = ?", o.data, ^object.data["id"])
)
|> order_by([o], asc: o.id)
if opts[:self_only] do
actor = object.data["actor"]
where(query, [o], fragment("(?)->>'actor' = ?", o.data, ^actor))
else
query
end
end
def self_replies(object, opts \\ []),
do: replies(object, Keyword.put(opts, :self_only, true))
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object.Containment do defmodule Pleroma.Object.Containment do
@ -39,15 +39,8 @@ def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor)
defp compare_uris(_, %URI{scheme: "tag"}), do: :ok defp compare_uris(_, %URI{scheme: "tag"}), do: :ok
end end
defp compare_uris(%URI{} = id_uri, %URI{} = other_uri) do defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
if id_uri.host == other_uri.host do defp compare_uris(_id_uri, _other_uri), do: :error
:ok
else
:error
end
end
defp compare_uris(_, _), do: :error
@doc """ @doc """
Checks that an imported AP object's actor matches the domain it came from. Checks that an imported AP object's actor matches the domain it came from.

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Object.Fetcher do defmodule Pleroma.Object.Fetcher do
@ -10,6 +10,7 @@ defmodule Pleroma.Object.Fetcher do
alias Pleroma.Signature alias Pleroma.Signature
alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
require Logger require Logger
require Pleroma.Constants require Pleroma.Constants
@ -59,20 +60,23 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
end end
end end
# TODO: # Note: will create a Create activity, which we need internally at the moment.
# This will create a Create activity, which we need internally at the moment.
def fetch_object_from_id(id, options \\ []) do def fetch_object_from_id(id, options \\ []) do
with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
{:normalize, nil} <- {:normalize, Object.normalize(data, false)}, {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
{_, nil} <- {:normalize, Object.normalize(data, false)},
params <- prepare_activity_params(data), params <- prepare_activity_params(data),
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)}, {_, :ok} <- {:containment, Containment.contain_origin(id, params)},
{:transmogrifier, {:ok, activity}} <- {_, {:ok, activity}} <-
{:transmogrifier, Transmogrifier.handle_incoming(params, options)}, {:transmogrifier, Transmogrifier.handle_incoming(params, options)},
{:object, _data, %Object{} = object} <- {_, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, false)} do {:object, data, Object.normalize(activity, false)} do
{:ok, object} {:ok, object}
else else
{:allowed_depth, false} ->
{:error, "Max thread distance exceeded."}
{:containment, _} -> {:containment, _} ->
{:error, "Object containment failed."} {:error, "Object containment failed."}

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Pagination do defmodule Pleroma.Pagination do
@ -12,11 +12,15 @@ defmodule Pleroma.Pagination do
alias Pleroma.Repo alias Pleroma.Repo
@type type :: :keyset | :offset
@default_limit 20 @default_limit 20
@max_limit 40
@page_keys ["max_id", "min_id", "limit", "since_id", "order"] @page_keys ["max_id", "min_id", "limit", "since_id", "order"]
def page_keys, do: @page_keys def page_keys, do: @page_keys
@spec fetch_paginated(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil) def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil)
def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do
@ -57,6 +61,7 @@ def fetch_paginated(query, params, :offset, table_binding) do
|> Repo.all() |> Repo.all()
end end
@spec paginate(Ecto.Query.t(), map(), type(), atom() | nil) :: [Ecto.Schema.t()]
def paginate(query, options, method \\ :keyset, table_binding \\ nil) def paginate(query, options, method \\ :keyset, table_binding \\ nil)
def paginate(query, options, :keyset, table_binding) do def paginate(query, options, :keyset, table_binding) do
@ -130,7 +135,11 @@ defp restrict(query, :offset, %{offset: offset}, _table_binding) do
end end
defp restrict(query, :limit, options, _table_binding) do defp restrict(query, :limit, options, _table_binding) do
limit = Map.get(options, :limit, @default_limit) limit =
case Map.get(options, :limit, @default_limit) do
limit when limit < @max_limit -> limit
_ -> @max_limit
end
query query
|> limit(^limit) |> limit(^limit)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.HTTPSecurityPlug do defmodule Pleroma.Plugs.HTTPSecurityPlug do

View file

@ -1,9 +1,10 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
import Plug.Conn import Plug.Conn
import Phoenix.Controller, only: [get_format: 1, text: 2]
require Logger require Logger
def init(options) do def init(options) do
@ -15,25 +16,27 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
end end
def call(conn, _opts) do def call(conn, _opts) do
headers = get_req_header(conn, "signature") if get_format(conn) == "activity+json" do
signature = Enum.at(headers, 0) conn
|> maybe_assign_valid_signature()
|> maybe_require_signature()
else
conn
end
end
if signature do defp maybe_assign_valid_signature(conn) do
if has_signature_header?(conn) do
# set (request-target) header to the appropriate value # set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed # we also replace the digest header with the one we computed
conn = request_target = String.downcase("#{conn.method}") <> " #{conn.request_path}"
conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
conn = conn =
if conn.assigns[:digest] do conn
conn |> put_req_header("(request-target)", request_target)
|> put_req_header("digest", conn.assigns[:digest]) |> case do
else %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
conn conn -> conn
end end
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn)) assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
@ -42,4 +45,21 @@ def call(conn, _opts) do
conn conn
end end
end end
defp has_signature_header?(conn) do
conn |> get_req_header("signature") |> Enum.at(0, false)
end
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
defp maybe_require_signature(conn) do
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
conn
|> put_status(:unauthorized)
|> text("Request not signed")
|> halt()
else
conn
end
end
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.OAuthScopesPlug do defmodule Pleroma.Plugs.OAuthScopesPlug do

View file

@ -7,8 +7,8 @@ def start_link(init_arg) do
DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
end end
def add_limiter(limiter_name, expiration) do def add_or_return_limiter(limiter_name, expiration) do
{:ok, _pid} = result =
DynamicSupervisor.start_child( DynamicSupervisor.start_child(
__MODULE__, __MODULE__,
%{ %{
@ -28,6 +28,12 @@ def add_limiter(limiter_name, expiration) do
]} ]}
} }
) )
case result do
{:ok, _pid} = result -> result
{:error, {:already_started, pid}} -> {:ok, pid}
_ -> result
end
end end
@impl true @impl true

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.RateLimiter do defmodule Pleroma.Plugs.RateLimiter do
@ -7,12 +7,14 @@ defmodule Pleroma.Plugs.RateLimiter do
## Configuration ## Configuration
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where: A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration.
The basic configuration is a tuple where:
* The first element: `scale` (Integer). The time scale in milliseconds. * The first element: `scale` (Integer). The time scale in milliseconds.
* The second element: `limit` (Integer). How many requests to limit in the time scale provided. * The second element: `limit` (Integer). How many requests to limit in the time scale provided.
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated. It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a
list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
To disable a limiter set its value to `nil`. To disable a limiter set its value to `nil`.
@ -64,91 +66,102 @@ defmodule Pleroma.Plugs.RateLimiter do
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers
import Plug.Conn import Plug.Conn
alias Pleroma.Config
alias Pleroma.Plugs.RateLimiter.LimiterSupervisor alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
alias Pleroma.User alias Pleroma.User
require Logger require Logger
def init(opts) do @doc false
limiter_name = Keyword.get(opts, :name) def init(plug_opts) do
plug_opts
end
case Pleroma.Config.get([:rate_limit, limiter_name]) do def call(conn, plug_opts) do
nil -> if disabled?() do
nil handle_disabled(conn)
else
config -> action_settings = action_settings(plug_opts)
name_root = Keyword.get(opts, :bucket_name, limiter_name) handle(conn, action_settings)
%{
name: name_root,
limits: config,
opts: opts
}
end end
end end
# Do not limit if there is no limiter configuration defp handle_disabled(conn) do
def call(conn, nil), do: conn if Config.get(:env) == :prod do
Logger.warn("Rate limiter is disabled for localhost/socket")
end
def call(conn, settings) do conn
case disabled?() do end
true ->
if Pleroma.Config.get(:env) == :prod,
do: Logger.warn("Rate limiter is disabled for localhost/socket")
defp handle(conn, nil), do: conn
defp handle(conn, action_settings) do
action_settings
|> incorporate_conn_info(conn)
|> check_rate()
|> case do
{:ok, _count} ->
conn conn
false -> {:error, _count} ->
settings render_throttled_error(conn)
|> incorporate_conn_info(conn)
|> check_rate()
|> case do
{:ok, _count} ->
conn
{:error, _count} ->
render_throttled_error(conn)
end
end end
end end
def disabled? do def disabled? do
localhost_or_socket = localhost_or_socket =
Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip]) Config.get([Pleroma.Web.Endpoint, :http, :ip])
|> Tuple.to_list() |> Tuple.to_list()
|> Enum.join(".") |> Enum.join(".")
|> String.match?(~r/^local|^127.0.0.1/) |> String.match?(~r/^local|^127.0.0.1/)
remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled]) remote_ip_disabled = not Config.get([Pleroma.Plugs.RemoteIp, :enabled])
localhost_or_socket and remote_ip_disabled localhost_or_socket and remote_ip_disabled
end end
def inspect_bucket(conn, name_root, settings) do @inspect_bucket_not_found {:error, :not_found}
settings =
settings
|> incorporate_conn_info(conn)
bucket_name = make_bucket_name(%{settings | name: name_root}) def inspect_bucket(conn, bucket_name_root, plug_opts) do
key_name = make_key_name(settings) with %{name: _} = action_settings <- action_settings(plug_opts) do
limit = get_limits(settings) action_settings = incorporate_conn_info(action_settings, conn)
bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root})
key_name = make_key_name(action_settings)
limit = get_limits(action_settings)
case Cachex.get(bucket_name, key_name) do case Cachex.get(bucket_name, key_name) do
{:error, :no_cache} -> {:error, :no_cache} ->
{:err, :not_found} @inspect_bucket_not_found
{:ok, nil} -> {:ok, nil} ->
{0, limit} {0, limit}
{:ok, value} -> {:ok, value} ->
{value, limit - value} {value, limit - value}
end
else
_ -> @inspect_bucket_not_found
end end
end end
defp check_rate(settings) do def action_settings(plug_opts) do
bucket_name = make_bucket_name(settings) with limiter_name when is_atom(limiter_name) <- plug_opts[:name],
key_name = make_key_name(settings) limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do
limit = get_limits(settings) bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name)
%{
name: bucket_name_root,
limits: limits,
opts: plug_opts
}
end
end
defp check_rate(action_settings) do
bucket_name = make_bucket_name(action_settings)
key_name = make_key_name(action_settings)
limit = get_limits(action_settings)
case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
{:commit, value} -> {:commit, value} ->
@ -158,8 +171,8 @@ defp check_rate(settings) do
{:error, value} {:error, value}
{:error, :no_cache} -> {:error, :no_cache} ->
initialize_buckets(settings) initialize_buckets!(action_settings)
check_rate(settings) check_rate(action_settings)
end end
end end
@ -169,16 +182,19 @@ defp increment_value(val, limit) when val >= limit, do: {:ignore, val}
defp increment_value(val, _limit), do: {:commit, val + 1} defp increment_value(val, _limit), do: {:commit, val + 1}
defp incorporate_conn_info(settings, %{assigns: %{user: %User{id: user_id}}, params: params}) do defp incorporate_conn_info(action_settings, %{
Map.merge(settings, %{ assigns: %{user: %User{id: user_id}},
params: params
}) do
Map.merge(action_settings, %{
mode: :user, mode: :user,
conn_params: params, conn_params: params,
conn_info: "#{user_id}" conn_info: "#{user_id}"
}) })
end end
defp incorporate_conn_info(settings, %{params: params} = conn) do defp incorporate_conn_info(action_settings, %{params: params} = conn) do
Map.merge(settings, %{ Map.merge(action_settings, %{
mode: :anon, mode: :anon,
conn_params: params, conn_params: params,
conn_info: "#{ip(conn)}" conn_info: "#{ip(conn)}"
@ -197,10 +213,10 @@ defp render_throttled_error(conn) do
|> halt() |> halt()
end end
defp make_key_name(settings) do defp make_key_name(action_settings) do
"" ""
|> attach_params(settings) |> attach_selected_params(action_settings)
|> attach_identity(settings) |> attach_identity(action_settings)
end end
defp get_scale(_, {scale, _}), do: scale defp get_scale(_, {scale, _}), do: scale
@ -215,28 +231,35 @@ defp get_limits(%{mode: :user, limits: [_, {_, limit}]}), do: limit
defp get_limits(%{limits: [{_, limit}, _]}), do: limit defp get_limits(%{limits: [{_, limit}, _]}), do: limit
defp make_bucket_name(%{mode: :user, name: name_root}), defp make_bucket_name(%{mode: :user, name: bucket_name_root}),
do: user_bucket_name(name_root) do: user_bucket_name(bucket_name_root)
defp make_bucket_name(%{mode: :anon, name: name_root}), defp make_bucket_name(%{mode: :anon, name: bucket_name_root}),
do: anon_bucket_name(name_root) do: anon_bucket_name(bucket_name_root)
defp attach_params(input, %{conn_params: conn_params, opts: opts}) do defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do
param_string = params_string =
opts plug_opts
|> Keyword.get(:params, []) |> Keyword.get(:params, [])
|> Enum.sort() |> Enum.sort()
|> Enum.map(&Map.get(conn_params, &1, "")) |> Enum.map(&Map.get(conn_params, &1, ""))
|> Enum.join(":") |> Enum.join(":")
"#{input}#{param_string}" [input, params_string]
|> Enum.join(":")
|> String.replace_leading(":", "")
end end
defp initialize_buckets(%{name: _name, limits: nil}), do: :ok defp initialize_buckets!(%{name: _name, limits: nil}), do: :ok
defp initialize_buckets(%{name: name, limits: limits}) do defp initialize_buckets!(%{name: name, limits: limits}) do
LimiterSupervisor.add_limiter(anon_bucket_name(name), get_scale(:anon, limits)) {:ok, _pid} =
LimiterSupervisor.add_limiter(user_bucket_name(name), get_scale(:user, limits)) LimiterSupervisor.add_or_return_limiter(anon_bucket_name(name), get_scale(:anon, limits))
{:ok, _pid} =
LimiterSupervisor.add_or_return_limiter(user_bucket_name(name), get_scale(:user, limits))
:ok
end end
defp attach_identity(base, %{mode: :user, conn_info: conn_info}), defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
@ -245,6 +268,6 @@ defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
defp attach_identity(base, %{mode: :anon, conn_info: conn_info}), defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),
do: "ip:#{base}:#{conn_info}" do: "ip:#{base}:#{conn_info}"
defp user_bucket_name(name_root), do: "user:#{name_root}" |> String.to_atom() defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom()
defp anon_bucket_name(name_root), do: "anon:#{name_root}" |> String.to_atom() defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom()
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.RemoteIp do defmodule Pleroma.Plugs.RemoteIp do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserEnabledPlug do defmodule Pleroma.Plugs.UserEnabledPlug do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.UserIsAdminPlug do defmodule Pleroma.Plugs.UserIsAdminPlug do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Repo do defmodule Pleroma.Repo do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ScheduledActivity do defmodule Pleroma.ScheduledActivity do

View file

@ -1,9 +1,10 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Stats do defmodule Pleroma.Stats do
import Ecto.Query import Ecto.Query
alias Pleroma.CounterCache
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -96,4 +97,21 @@ defp get_stat_data do
} }
} }
end end
def get_status_visibility_count do
counter_cache =
CounterCache.get_as_map([
"status_visibility_public",
"status_visibility_private",
"status_visibility_unlisted",
"status_visibility_direct"
])
%{
public: counter_cache["status_visibility_public"] || 0,
unlisted: counter_cache["status_visibility_unlisted"] || 0,
private: counter_cache["status_visibility_private"] || 0,
direct: counter_cache["status_visibility_direct"] || 0
}
end
end end

View file

@ -37,6 +37,7 @@ defmodule Pleroma.Upload do
Plug.Upload.t() Plug.Upload.t()
| (data_uri_string :: String.t()) | (data_uri_string :: String.t())
| {:from_local, name :: String.t(), id :: String.t(), path :: String.t()} | {:from_local, name :: String.t(), id :: String.t(), path :: String.t()}
| map()
@type option :: @type option ::
{:type, :avatar | :banner | :background} {:type, :avatar | :banner | :background}

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Local do defmodule Pleroma.Uploaders.Local do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.S3 do defmodule Pleroma.Uploaders.S3 do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Uploaders.Uploader do defmodule Pleroma.Uploaders.Uploader do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User do defmodule Pleroma.User do
@ -749,9 +749,18 @@ def invalidate_cache(user) do
Cachex.del(:user_cache, "nickname:#{user.nickname}") Cachex.del(:user_cache, "nickname:#{user.nickname}")
end end
@spec get_cached_by_ap_id(String.t()) :: User.t() | nil
def get_cached_by_ap_id(ap_id) do def get_cached_by_ap_id(ap_id) do
key = "ap_id:#{ap_id}" key = "ap_id:#{ap_id}"
Cachex.fetch!(:user_cache, key, fn _ -> get_by_ap_id(ap_id) end)
with {:ok, nil} <- Cachex.get(:user_cache, key),
user when not is_nil(user) <- get_by_ap_id(ap_id),
{:ok, true} <- Cachex.put(:user_cache, key, user) do
user
else
{:ok, user} -> user
nil -> nil
end
end end
def get_cached_by_id(id) do def get_cached_by_id(id) do
@ -853,14 +862,14 @@ def get_followers_query(user, page) do
@spec get_followers_query(User.t()) :: Ecto.Query.t() @spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil) def get_followers_query(user), do: get_followers_query(user, nil)
@spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} @spec get_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do def get_followers(user, page \\ nil) do
user user
|> get_followers_query(page) |> get_followers_query(page)
|> Repo.all() |> Repo.all()
end end
@spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())} @spec get_external_followers(User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_external_followers(user, page \\ nil) do def get_external_followers(user, page \\ nil) do
user user
|> get_followers_query(page) |> get_followers_query(page)
@ -1304,7 +1313,6 @@ def perform(:delete, %User{} = user) do
Repo.delete(user) Repo.delete(user)
end end
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:fetch_initial_posts, %User{} = user) do def perform(:fetch_initial_posts, %User{} = user) do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages]) pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
@ -1336,7 +1344,6 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
) )
end end
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
def perform(:follow_import, %User{} = follower, followed_identifiers) def perform(:follow_import, %User{} = follower, followed_identifiers)
when is_list(followed_identifiers) do when is_list(followed_identifiers) do
Enum.map( Enum.map(

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Query do defmodule Pleroma.User.Query do
@ -48,7 +48,7 @@ defmodule Pleroma.User.Query do
followers: User.t(), followers: User.t(),
friends: User.t(), friends: User.t(),
recipients_from_activity: [String.t()], recipients_from_activity: [String.t()],
nickname: [String.t()], nickname: [String.t()] | String.t(),
ap_id: [String.t()], ap_id: [String.t()],
order_by: term(), order_by: term(),
select: term(), select: term(),

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.Search do defmodule Pleroma.User.Search do
@ -33,9 +33,15 @@ defp format_query(query_string) do
# Strip the beginning @ off if there is a query # Strip the beginning @ off if there is a query
query_string = String.trim_leading(query_string, "@") query_string = String.trim_leading(query_string, "@")
with [name, domain] <- String.split(query_string, "@"), with [name, domain] <- String.split(query_string, "@") do
formatted_domain <- String.replace(domain, ~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "") do encoded_domain =
name <> "@" <> to_string(:idna.encode(formatted_domain)) domain
|> String.replace(~r/[!-\-|@|[-`|{-~|\/|:|\s]+/, "")
|> String.to_charlist()
|> :idna.encode()
|> to_string()
name <> "@" <> encoded_domain
else else
_ -> query_string _ -> query_string
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserRelationship do defmodule Pleroma.UserRelationship do

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.Activity.Ir.Topics alias Pleroma.Activity.Ir.Topics
alias Pleroma.ActivityExpiration alias Pleroma.ActivityExpiration
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Constants
alias Pleroma.Conversation alias Pleroma.Conversation
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Notification alias Pleroma.Notification
@ -125,6 +126,7 @@ def increase_poll_votes_if_vote(%{
def increase_poll_votes_if_vote(_create_data), do: :noop def increase_poll_votes_if_vote(_create_data), do: :noop
@spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
with nil <- Activity.normalize(map), with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map, fake), map <- lazy_put_activity_defaults(map, fake),
@ -242,12 +244,19 @@ def stream_out(_activity) do
:noop :noop
end end
def create(%{to: to, actor: actor, context: context, object: object} = params, fake \\ false) do @spec create(map(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
def create(params, fake \\ false) do
with {:ok, result} <- Repo.transaction(fn -> do_create(params, fake) end) do
result
end
end
defp do_create(%{to: to, actor: actor, context: context, object: object} = params, fake) do
additional = params[:additional] || %{} additional = params[:additional] || %{}
# only accept false as false value # only accept false as false value
local = !(params[:local] == false) local = !(params[:local] == false)
published = params[:published] published = params[:published]
quick_insert? = Pleroma.Config.get([:env]) == :benchmark quick_insert? = Config.get([:env]) == :benchmark
with create_data <- with create_data <-
make_create_data( make_create_data(
@ -270,10 +279,11 @@ def create(%{to: to, actor: actor, context: context, object: object} = params, f
{:ok, activity} {:ok, activity}
{:error, message} -> {:error, message} ->
{:error, message} Repo.rollback(message)
end end
end end
@spec listen(map()) :: {:ok, Activity.t()} | {:error, any()}
def listen(%{to: to, actor: actor, context: context, object: object} = params) do def listen(%{to: to, actor: actor, context: context, object: object} = params) do
additional = params[:additional] || %{} additional = params[:additional] || %{}
# only accept false as false value # only accept false as false value
@ -288,20 +298,20 @@ def listen(%{to: to, actor: actor, context: context, object: object} = params) d
{:ok, activity} <- insert(listen_data, local), {:ok, activity} <- insert(listen_data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
else
{:error, message} ->
{:error, message}
end end
end end
@spec accept(map()) :: {:ok, Activity.t()} | {:error, any()}
def accept(params) do def accept(params) do
accept_or_reject("Accept", params) accept_or_reject("Accept", params)
end end
@spec reject(map()) :: {:ok, Activity.t()} | {:error, any()}
def reject(params) do def reject(params) do
accept_or_reject("Reject", params) accept_or_reject("Reject", params)
end end
@spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
local = Map.get(params, :local, true) local = Map.get(params, :local, true)
activity_id = Map.get(params, :activity_id, nil) activity_id = Map.get(params, :activity_id, nil)
@ -315,6 +325,7 @@ def accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
end end
end end
@spec update(map()) :: {:ok, Activity.t()} | {:error, any()}
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
local = !(params[:local] == false) local = !(params[:local] == false)
activity_id = params[:activity_id] activity_id = params[:activity_id]
@ -333,7 +344,16 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
end end
end end
@spec react_with_emoji(User.t(), Object.t(), String.t(), keyword()) ::
{:ok, Activity.t(), Object.t()} | {:error, any()}
def react_with_emoji(user, object, emoji, options \\ []) do def react_with_emoji(user, object, emoji, options \\ []) do
with {:ok, result} <-
Repo.transaction(fn -> do_react_with_emoji(user, object, emoji, options) end) do
result
end
end
defp do_react_with_emoji(user, object, emoji, options) do
with local <- Keyword.get(options, :local, true), with local <- Keyword.get(options, :local, true),
activity_id <- Keyword.get(options, :activity_id, nil), activity_id <- Keyword.get(options, :activity_id, nil),
true <- Pleroma.Emoji.is_unicode_emoji?(emoji), true <- Pleroma.Emoji.is_unicode_emoji?(emoji),
@ -343,11 +363,21 @@ def react_with_emoji(user, object, emoji, options \\ []) do
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity, object} {:ok, activity, object}
else else
e -> {:error, e} false -> {:error, false}
{:error, error} -> Repo.rollback(error)
end end
end end
@spec unreact_with_emoji(User.t(), String.t(), keyword()) ::
{:ok, Activity.t(), Object.t()} | {:error, any()}
def unreact_with_emoji(user, reaction_id, options \\ []) do def unreact_with_emoji(user, reaction_id, options \\ []) do
with {:ok, result} <-
Repo.transaction(fn -> do_unreact_with_emoji(user, reaction_id, options) end) do
result
end
end
defp do_unreact_with_emoji(user, reaction_id, options) do
with local <- Keyword.get(options, :local, true), with local <- Keyword.get(options, :local, true),
activity_id <- Keyword.get(options, :activity_id, nil), activity_id <- Keyword.get(options, :activity_id, nil),
user_ap_id <- user.ap_id, user_ap_id <- user.ap_id,
@ -359,17 +389,25 @@ def unreact_with_emoji(user, reaction_id, options \\ []) do
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity, object} {:ok, activity, object}
else else
e -> {:error, e} {:error, error} -> Repo.rollback(error)
end end
end end
# TODO: This is weird, maybe we shouldn't check here if we can make the activity. # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
def like( @spec like(User.t(), Object.t(), String.t() | nil, boolean()) ::
%User{ap_id: ap_id} = user, {:ok, Activity.t(), Object.t()} | {:error, any()}
%Object{data: %{"id" => _}} = object, def like(user, object, activity_id \\ nil, local \\ true) do
activity_id \\ nil, with {:ok, result} <- Repo.transaction(fn -> do_like(user, object, activity_id, local) end) do
local \\ true result
) do end
end
defp do_like(
%User{ap_id: ap_id} = user,
%Object{data: %{"id" => _}} = object,
activity_id,
local
) do
with nil <- get_existing_like(ap_id, object), with nil <- get_existing_like(ap_id, object),
like_data <- make_like_data(user, object, activity_id), like_data <- make_like_data(user, object, activity_id),
{:ok, activity} <- insert(like_data, local), {:ok, activity} <- insert(like_data, local),
@ -377,12 +415,24 @@ def like(
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity, object} {:ok, activity, object}
else else
%Activity{} = activity -> {:ok, activity, object} %Activity{} = activity ->
error -> {:error, error} {:ok, activity, object}
{:error, error} ->
Repo.rollback(error)
end end
end end
@spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
with {:ok, result} <-
Repo.transaction(fn -> do_unlike(actor, object, activity_id, local) end) do
result
end
end
defp do_unlike(actor, object, activity_id, local) do
with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object), with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
unlike_data <- make_unlike_data(actor, like_activity, activity_id), unlike_data <- make_unlike_data(actor, like_activity, activity_id),
{:ok, unlike_activity} <- insert(unlike_data, local), {:ok, unlike_activity} <- insert(unlike_data, local),
@ -391,10 +441,13 @@ def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ tru
:ok <- maybe_federate(unlike_activity) do :ok <- maybe_federate(unlike_activity) do
{:ok, unlike_activity, like_activity, object} {:ok, unlike_activity, like_activity, object}
else else
_e -> {:ok, object} nil -> {:ok, object}
{:error, error} -> Repo.rollback(error)
end end
end end
@spec announce(User.t(), Object.t(), String.t() | nil, boolean(), boolean()) ::
{:ok, Activity.t(), Object.t()} | {:error, any()}
def announce( def announce(
%User{ap_id: _} = user, %User{ap_id: _} = user,
%Object{data: %{"id" => _}} = object, %Object{data: %{"id" => _}} = object,
@ -402,6 +455,13 @@ def announce(
local \\ true, local \\ true,
public \\ true public \\ true
) do ) do
with {:ok, result} <-
Repo.transaction(fn -> do_announce(user, object, activity_id, local, public) end) do
result
end
end
defp do_announce(user, object, activity_id, local, public) do
with true <- is_announceable?(object, user, public), with true <- is_announceable?(object, user, public),
announce_data <- make_announce_data(user, object, activity_id, public), announce_data <- make_announce_data(user, object, activity_id, public),
{:ok, activity} <- insert(announce_data, local), {:ok, activity} <- insert(announce_data, local),
@ -409,16 +469,26 @@ def announce(
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity, object} {:ok, activity, object}
else else
error -> {:error, error} false -> {:error, false}
{:error, error} -> Repo.rollback(error)
end end
end end
@spec unannounce(User.t(), Object.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
def unannounce( def unannounce(
%User{} = actor, %User{} = actor,
%Object{} = object, %Object{} = object,
activity_id \\ nil, activity_id \\ nil,
local \\ true local \\ true
) do ) do
with {:ok, result} <-
Repo.transaction(fn -> do_unannounce(actor, object, activity_id, local) end) do
result
end
end
defp do_unannounce(actor, object, activity_id, local) do
with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object), with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id), unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
{:ok, unannounce_activity} <- insert(unannounce_data, local), {:ok, unannounce_activity} <- insert(unannounce_data, local),
@ -427,30 +497,61 @@ def unannounce(
{:ok, object} <- remove_announce_from_object(announce_activity, object) do {:ok, object} <- remove_announce_from_object(announce_activity, object) do
{:ok, unannounce_activity, object} {:ok, unannounce_activity, object}
else else
_e -> {:ok, object} nil -> {:ok, object}
{:error, error} -> Repo.rollback(error)
end end
end end
@spec follow(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | {:error, any()}
def follow(follower, followed, activity_id \\ nil, local \\ true) do def follow(follower, followed, activity_id \\ nil, local \\ true) do
with {:ok, result} <-
Repo.transaction(fn -> do_follow(follower, followed, activity_id, local) end) do
result
end
end
defp do_follow(follower, followed, activity_id, local) do
with data <- make_follow_data(follower, followed, activity_id), with data <- make_follow_data(follower, followed, activity_id),
{:ok, activity} <- insert(data, local), {:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity), :ok <- maybe_federate(activity),
_ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do
{:ok, activity} {:ok, activity}
else
{:error, error} -> Repo.rollback(error)
end end
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 def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
with {:ok, result} <-
Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do
result
end
end
defp do_unfollow(follower, followed, activity_id, local) do
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity} <- insert(unfollow_data, local), {:ok, activity} <- insert(unfollow_data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
else
nil -> nil
{:error, error} -> Repo.rollback(error)
end end
end end
def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do @spec delete(User.t() | Object.t(), keyword()) :: {:ok, User.t() | Object.t()} | {:error, any()}
def delete(entity, options \\ []) do
with {:ok, result} <- Repo.transaction(fn -> do_delete(entity, options) end) do
result
end
end
defp do_delete(%User{ap_id: ap_id, follower_address: follower_address} = user, _) do
with data <- %{ with data <- %{
"to" => [follower_address], "to" => [follower_address],
"type" => "Delete", "type" => "Delete",
@ -463,7 +564,7 @@ def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do
end end
end end
def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ []) do defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options) do
local = Keyword.get(options, :local, true) local = Keyword.get(options, :local, true)
activity_id = Keyword.get(options, :activity_id, nil) activity_id = Keyword.get(options, :activity_id, nil)
actor = Keyword.get(options, :actor, actor) actor = Keyword.get(options, :actor, actor)
@ -488,11 +589,22 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options \\ [
{:ok, _actor} <- decrease_note_count_if_public(user, object), {:ok, _actor} <- decrease_note_count_if_public(user, object),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
else
{:error, error} ->
Repo.rollback(error)
end end
end end
@spec block(User.t(), User.t(), String.t() | nil, boolean) :: {:ok, Activity.t() | nil} @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 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
outgoing_blocks = Config.get([:activitypub, :outgoing_blocks]) outgoing_blocks = Config.get([:activitypub, :outgoing_blocks])
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked]) unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
@ -507,20 +619,32 @@ def block(blocker, blocked, activity_id \\ nil, local \\ true) do
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
else else
_e -> {:ok, nil} {:error, error} -> Repo.rollback(error)
end end
end end
@spec unblock(User.t(), User.t(), String.t() | nil, boolean()) ::
{:ok, Activity.t()} | {:error, any()} | nil
def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
with {:ok, result} <-
Repo.transaction(fn -> do_unblock(blocker, blocked, activity_id, local) end) do
result
end
end
defp do_unblock(blocker, blocked, activity_id, local) do
with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked), with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id), unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
{:ok, activity} <- insert(unblock_data, local), {:ok, activity} <- insert(unblock_data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
{:ok, activity} {:ok, activity}
else
nil -> nil
{:error, error} -> Repo.rollback(error)
end end
end end
@spec flag(map()) :: {:ok, Activity.t()} | any @spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
def flag( def flag(
%{ %{
actor: actor, actor: actor,
@ -557,6 +681,7 @@ def flag(
end end
end end
@spec move(User.t(), User.t(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
def move(%User{} = origin, %User{} = target, local \\ true) do def move(%User{} = origin, %User{} = target, local \\ true) do
params = %{ params = %{
"type" => "Move", "type" => "Move",
@ -582,7 +707,7 @@ def move(%User{} = origin, %User{} = target, local \\ true) do
end end
defp fetch_activities_for_context_query(context, opts) do defp fetch_activities_for_context_query(context, opts) do
public = [Pleroma.Constants.as_public()] public = [Constants.as_public()]
recipients = recipients =
if opts["user"], if opts["user"],
@ -627,10 +752,11 @@ def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
|> Repo.one() |> Repo.one()
end end
@spec fetch_public_activities(map(), Pagination.type()) :: [Activity.t()]
def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do def fetch_public_activities(opts \\ %{}, pagination \\ :keyset) do
opts = Map.drop(opts, ["user"]) opts = Map.drop(opts, ["user"])
[Pleroma.Constants.as_public()] [Constants.as_public()]
|> fetch_activities_query(opts) |> fetch_activities_query(opts)
|> restrict_unlisted() |> restrict_unlisted()
|> Pagination.fetch_paginated(opts, pagination) |> Pagination.fetch_paginated(opts, pagination)
@ -781,13 +907,18 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|> Enum.reverse() |> Enum.reverse()
end end
def fetch_instance_activities(params) do def fetch_statuses(reading_user, params) do
params = params =
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
|> Map.put("instance", params["instance"])
fetch_activities([Pleroma.Constants.as_public()], params, :offset) recipients =
user_activities_recipients(%{
"godmode" => params["godmode"],
"reading_user" => reading_user
})
fetch_activities(recipients, params, :offset)
|> Enum.reverse() |> Enum.reverse()
end end
@ -797,9 +928,9 @@ defp user_activities_recipients(%{"godmode" => true}) do
defp user_activities_recipients(%{"reading_user" => reading_user}) do defp user_activities_recipients(%{"reading_user" => reading_user}) do
if reading_user do if reading_user do
[Pleroma.Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)] [Constants.as_public()] ++ [reading_user.ap_id | User.following(reading_user)]
else else
[Pleroma.Constants.as_public()] [Constants.as_public()]
end end
end end
@ -1006,7 +1137,7 @@ defp restrict_unlisted(query) do
fragment( fragment(
"not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)", "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
activity.data, activity.data,
^[Pleroma.Constants.as_public()] ^[Constants.as_public()]
) )
) )
end end
@ -1180,7 +1311,7 @@ def fetch_activities(recipients, opts \\ %{}, pagination \\ :keyset) do
@doc """ @doc """
Fetch favorites activities of user with order by sort adds to favorites Fetch favorites activities of user with order by sort adds to favorites
""" """
@spec fetch_favourites(User.t(), map(), atom()) :: list(Activity.t()) @spec fetch_favourites(User.t(), map(), Pagination.type()) :: list(Activity.t())
def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do
user.ap_id user.ap_id
|> Activity.Queries.by_actor() |> Activity.Queries.by_actor()
@ -1218,7 +1349,7 @@ def fetch_activities_bounded_query(query, recipients, recipients_with_public) do
where: where:
fragment("? && ?", activity.recipients, ^recipients) or fragment("? && ?", activity.recipients, ^recipients) or
(fragment("? && ?", activity.recipients, ^recipients_with_public) and (fragment("? && ?", activity.recipients, ^recipients_with_public) and
^Pleroma.Constants.as_public() in activity.recipients) ^Constants.as_public() in activity.recipients)
) )
end end
@ -1234,6 +1365,7 @@ def fetch_activities_bounded(
|> Enum.reverse() |> Enum.reverse()
end end
@spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()}
def upload(file, opts \\ []) do def upload(file, opts \\ []) do
with {:ok, data} <- Upload.store(file, opts) do with {:ok, data} <- Upload.store(file, opts) do
obj_data = obj_data =
@ -1331,8 +1463,7 @@ defp normalize_counter(counter) when is_integer(counter), do: counter
defp normalize_counter(_), do: 0 defp normalize_counter(_), do: 0
defp maybe_update_follow_information(data) do defp maybe_update_follow_information(data) do
with {:enabled, true} <- with {:enabled, true} <- {:enabled, Config.get([:instance, :external_user_synchronization])},
{:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])},
{:ok, info} <- fetch_follow_information_for_user(data) do {:ok, info} <- fetch_follow_information_for_user(data) do
info = Map.merge(data[:info] || %{}, info) info = Map.merge(data[:info] || %{}, info)
Map.put(data, :info, info) Map.put(data, :info, info)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
@ -17,6 +17,7 @@ defp old_user?(%User{} = u) do
# does the post contain links? # does the post contain links?
defp contains_links?(%{"content" => content} = _object) do defp contains_links?(%{"content" => content} = _object) do
content content
|> Floki.parse_fragment!()
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl") |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")
|> Floki.attribute("a", "href") |> Floki.attribute("a", "href")
|> length() > 0 |> length() > 0

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do

View file

@ -1,16 +1,15 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
require Pleroma.Constants require Pleroma.Constants
@moduledoc "Filter activities depending on their age" @moduledoc "Filter activities depending on their age"
@behaviour MRF @behaviour Pleroma.Web.ActivityPub.MRF
defp check_date(%{"published" => published} = message) do defp check_date(%{"published" => published} = message) do
with %DateTime{} = now <- DateTime.utc_now(), with %DateTime{} = now <- DateTime.utc_now(),

View file

@ -1,12 +1,12 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance" @moduledoc "Filter activities depending on their origin instance"
@behaviour MRF @behaviour Pleroma.Web.ActivityPub.MRF
require Pleroma.Constants require Pleroma.Constants

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do
require Logger require Logger
@behaviour MRF @behaviour Pleroma.Web.ActivityPub.MRF
defp lookup_subchain(actor) do defp lookup_subchain(actor) do
with matches <- Config.get([:mrf_subchain, :match_actor]), with matches <- Config.get([:mrf_subchain, :match_actor]),

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Publisher do defmodule Pleroma.Web.ActivityPub.Publisher do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Relay do defmodule Pleroma.Web.ActivityPub.Relay do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Transmogrifier do defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@ -156,10 +156,11 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do when not is_nil(in_reply_to) do
in_reply_to_id = prepare_in_reply_to(in_reply_to) in_reply_to_id = prepare_in_reply_to(in_reply_to)
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id) object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
depth = (options[:depth] || 0) + 1
if Federator.allowed_incoming_reply_depth?(options[:depth]) do if Federator.allowed_thread_distance?(depth) do
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options), with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
%Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do %Activity{} <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object object
|> Map.put("inReplyTo", replied_object.data["id"]) |> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
@ -312,7 +313,7 @@ def fix_type(object, options \\ [])
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do when is_binary(reply_id) do
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]), with true <- Federator.allowed_thread_distance?(options[:depth]),
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
Map.put(object, "type", "Answer") Map.put(object, "type", "Answer")
else else
@ -406,8 +407,7 @@ def handle_incoming(
with nil <- Activity.get_create_by_object_ap_id(object["id"]), 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(data["actor"]) do
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) object = fix_object(object, options)
object = fix_object(data["object"], options)
params = %{ params = %{
to: data["to"], to: data["to"],
@ -424,7 +424,20 @@ def handle_incoming(
]) ])
} }
ActivityPub.create(params) with {:ok, created_activity} <- ActivityPub.create(params) do
reply_depth = (options[:depth] || 0) + 1
if Federator.allowed_thread_distance?(reply_depth) do
for reply_id <- replies(object) do
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
"id" => reply_id,
"depth" => reply_depth
})
end
end
{:ok, created_activity}
end
else else
%Activity{} = activity -> {:ok, activity} %Activity{} = activity -> {:ok, activity}
_e -> :error _e -> :error
@ -442,7 +455,8 @@ def handle_incoming(
|> fix_addressing |> fix_addressing
with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
options = Keyword.put(options, :depth, (options[:depth] || 0) + 1) reply_depth = (options[:depth] || 0) + 1
options = Keyword.put(options, :depth, reply_depth)
object = fix_object(object, options) object = fix_object(object, options)
params = %{ params = %{
@ -903,6 +917,50 @@ def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_r
def set_reply_to_uri(obj), do: obj def set_reply_to_uri(obj), do: obj
@doc """
Serialized Mastodon-compatible `replies` collection containing _self-replies_.
Based on Mastodon's ActivityPub::NoteSerializer#replies.
"""
def set_replies(obj_data) do
replies_uris =
with limit when limit > 0 <-
Pleroma.Config.get([:activitypub, :note_replies_output_limit], 0),
%Object{} = object <- Object.get_cached_by_ap_id(obj_data["id"]) do
object
|> Object.self_replies()
|> select([o], fragment("?->>'id'", o.data))
|> limit(^limit)
|> Repo.all()
else
_ -> []
end
set_replies(obj_data, replies_uris)
end
defp set_replies(obj, []) do
obj
end
defp set_replies(obj, replies_uris) do
replies_collection = %{
"type" => "Collection",
"items" => replies_uris
}
Map.merge(obj, %{"replies" => replies_collection})
end
def replies(%{"replies" => %{"first" => %{"items" => items}}}) when not is_nil(items) do
items
end
def replies(%{"replies" => %{"items" => items}}) when not is_nil(items) do
items
end
def replies(_), do: []
# Prepares the object of an outgoing create activity. # Prepares the object of an outgoing create activity.
def prepare_object(object) do def prepare_object(object) do
object object
@ -914,6 +972,7 @@ def prepare_object(object) do
|> prepare_attachments |> prepare_attachments
|> set_conversation |> set_conversation
|> set_reply_to_uri |> set_reply_to_uri
|> set_replies
|> strip_internal_fields |> strip_internal_fields
|> strip_internal_tags |> strip_internal_tags
|> set_type |> set_type

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.Utils do defmodule Pleroma.Web.ActivityPub.Utils do
@ -45,8 +45,8 @@ def normalize_params(params) do
Map.put(params, "actor", get_ap_id(params["actor"])) Map.put(params, "actor", get_ap_id(params["actor"]))
end end
@spec determine_explicit_mentions(map()) :: map() @spec determine_explicit_mentions(map()) :: [any]
def determine_explicit_mentions(%{"tag" => tag} = _) when is_list(tag) do def determine_explicit_mentions(%{"tag" => tag}) when is_list(tag) do
Enum.flat_map(tag, fn Enum.flat_map(tag, fn
%{"type" => "Mention", "href" => href} -> [href] %{"type" => "Mention", "href" => href} -> [href]
_ -> [] _ -> []
@ -427,7 +427,7 @@ defp fetch_likes(object) do
@doc """ @doc """
Updates a follow activity's state (for locked accounts). Updates a follow activity's state (for locked accounts).
""" """
@spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity} | {:error, any()} @spec update_follow_state_for_all(Activity.t(), String.t()) :: {:ok, Activity | nil}
def update_follow_state_for_all( def update_follow_state_for_all(
%Activity{data: %{"actor" => actor, "object" => object}} = activity, %Activity{data: %{"actor" => actor, "object" => object}} = activity,
state state

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIController do defmodule Pleroma.Web.AdminAPI.AdminAPIController do
@ -8,10 +8,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3] import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.ConfigDB alias Pleroma.ConfigDB
alias Pleroma.ModerationLog alias Pleroma.ModerationLog
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ReportNote alias Pleroma.ReportNote
alias Pleroma.Stats
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserInviteToken alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -97,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read"], admin: true} %{scopes: ["read"], admin: true}
when action in [:config_show, :list_log] when action in [:config_show, :list_log, :stats]
) )
plug( plug(
@ -242,13 +244,15 @@ def user_show(conn, %{"nickname" => nickname}) do
end end
def list_instance_statuses(conn, %{"instance" => instance} = params) do def list_instance_statuses(conn, %{"instance" => instance} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params) {page, page_size} = page_params(params)
activities = activities =
ActivityPub.fetch_instance_activities(%{ ActivityPub.fetch_statuses(nil, %{
"instance" => instance, "instance" => instance,
"limit" => page_size, "limit" => page_size,
"offset" => (page - 1) * page_size "offset" => (page - 1) * page_size,
"exclude_reblogs" => !with_reblogs && "true"
}) })
conn conn
@ -257,6 +261,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
end end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do def list_user_statuses(conn, %{"nickname" => nickname} = params) do
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
godmode = params["godmode"] == "true" || params["godmode"] == 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) do
@ -265,7 +270,8 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
activities = activities =
ActivityPub.fetch_user_activities(user, nil, %{ ActivityPub.fetch_user_activities(user, nil, %{
"limit" => page_size, "limit" => page_size,
"godmode" => godmode "godmode" => godmode,
"exclude_reblogs" => !with_reblogs && "true"
}) })
conn conn
@ -570,8 +576,8 @@ def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target})
@doc "Sends registration invite via email" @doc "Sends registration invite via email"
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
with true <- with true <-
Pleroma.Config.get([:instance, :invites_enabled]) && Config.get([:instance, :invites_enabled]) &&
!Pleroma.Config.get([:instance, :registrations_open]), !Config.get([:instance, :registrations_open]),
{:ok, invite_token} <- UserInviteToken.create_invite(), {:ok, invite_token} <- UserInviteToken.create_invite(),
email <- email <-
Pleroma.Emails.UserEmail.user_invitation_email( Pleroma.Emails.UserEmail.user_invitation_email(
@ -739,6 +745,24 @@ def report_notes_delete(%{assigns: %{user: user}} = conn, %{
end end
end end
def list_statuses(%{assigns: %{user: admin}} = conn, params) do
godmode = params["godmode"] == "true" || params["godmode"] == true
local_only = params["local_only"] == "true" || params["local_only"] == true
{page, page_size} = page_params(params)
activities =
ActivityPub.fetch_statuses(admin, %{
"godmode" => godmode,
"local_only" => local_only,
"limit" => page_size,
"offset" => (page - 1) * page_size
})
conn
|> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity})
end
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
{:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
@ -808,7 +832,7 @@ def config_show(conn, _params) do
configs = ConfigDB.get_all_as_keyword() configs = ConfigDB.get_all_as_keyword()
merged = merged =
Pleroma.Config.Holder.config() Config.Holder.config()
|> ConfigDB.merge(configs) |> ConfigDB.merge(configs)
|> Enum.map(fn {group, values} -> |> Enum.map(fn {group, values} ->
Enum.map(values, fn {key, value} -> Enum.map(values, fn {key, value} ->
@ -838,7 +862,16 @@ def config_show(conn, _params) do
end) end)
|> List.flatten() |> List.flatten()
json(conn, %{configs: merged}) response = %{configs: merged}
response =
if Restarter.Pleroma.need_reboot?() do
Map.put(response, :need_reboot, true)
else
response
end
json(conn, response)
end end
end end
@ -863,20 +896,26 @@ def config_update(conn, %{"configs" => configs}) do
Ecto.get_meta(config, :state) == :deleted Ecto.get_meta(config, :state) == :deleted
end) end)
Pleroma.Config.TransferTask.load_and_update_env(deleted, false) Config.TransferTask.load_and_update_env(deleted, false)
need_reboot? = need_reboot? =
Enum.any?(updated, fn config -> Restarter.Pleroma.need_reboot?() ||
group = ConfigDB.from_string(config.group) Enum.any?(updated, fn config ->
key = ConfigDB.from_string(config.key) group = ConfigDB.from_string(config.group)
value = ConfigDB.from_binary(config.value) key = ConfigDB.from_string(config.key)
Pleroma.Config.TransferTask.pleroma_need_restart?(group, key, value) value = ConfigDB.from_binary(config.value)
end) Config.TransferTask.pleroma_need_restart?(group, key, value)
end)
response = %{configs: updated} response = %{configs: updated}
response = response =
if need_reboot?, do: Map.put(response, :need_reboot, need_reboot?), else: response if need_reboot? do
Restarter.Pleroma.need_reboot()
Map.put(response, :need_reboot, need_reboot?)
else
response
end
conn conn
|> put_view(ConfigView) |> put_view(ConfigView)
@ -886,18 +925,14 @@ def config_update(conn, %{"configs" => configs}) do
def restart(conn, _params) do def restart(conn, _params) do
with :ok <- configurable_from_database(conn) do with :ok <- configurable_from_database(conn) do
if Pleroma.Config.get(:env) == :test do Restarter.Pleroma.restart(Config.get(:env), 50)
Logger.warn("pleroma restarted")
else
send(Restarter.Pleroma, {:restart, 50})
end
json(conn, %{}) json(conn, %{})
end end
end end
defp configurable_from_database(conn) do defp configurable_from_database(conn) do
if Pleroma.Config.get(:configurable_from_database) do if Config.get(:configurable_from_database) do
:ok :ok
else else
errors( errors(
@ -941,6 +976,13 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
conn |> json("") conn |> json("")
end end
def stats(conn, _) do
count = Stats.get_status_visibility_count()
conn
|> json(%{"status_visibility" => count})
end
def errors(conn, {:error, :not_found}) do def errors(conn, {:error, :not_found}) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.Search do defmodule Pleroma.Web.AdminAPI.Search do
@ -18,7 +18,11 @@ defmacro not_empty_string(string) do
@spec user(map()) :: {:ok, [User.t()], pos_integer()} @spec user(map()) :: {:ok, [User.t()], pos_integer()}
def user(params \\ %{}) do def user(params \\ %{}) do
query = User.Query.build(params) |> order_by([u], u.nickname) query =
params
|> Map.drop([:page, :page_size])
|> User.Query.build()
|> order_by([u], u.nickname)
paginated_query = paginated_query =
User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size) User.Query.paginate(query, params[:page] || 1, params[:page_size] || @page_size)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ConfigView do defmodule Pleroma.Web.AdminAPI.ConfigView do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.StatusView do defmodule Pleroma.Web.AdminAPI.StatusView do
@ -10,7 +10,7 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
alias Pleroma.User alias Pleroma.User
def render("index.json", opts) do def render("index.json", opts) do
render_many(opts.activities, __MODULE__, "show.json", opts) safe_render_many(opts.activities, __MODULE__, "show.json", opts)
end end
def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI do defmodule Pleroma.Web.CommonAPI do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CommonAPI.Utils do defmodule Pleroma.Web.CommonAPI.Utils do
@ -228,9 +228,9 @@ def make_content_html(
data, data,
visibility visibility
) do ) do
no_attachment_links = attachment_links =
data data
|> Map.get("no_attachment_links", Config.get([:instance, :no_attachment_links])) |> Map.get("attachment_links", Config.get([:instance, :attachment_links]))
|> truthy_param?() |> truthy_param?()
content_type = get_content_type(data["content_type"]) content_type = get_content_type(data["content_type"])
@ -244,7 +244,7 @@ def make_content_html(
status status
|> format_input(content_type, options) |> format_input(content_type, options)
|> maybe_add_attachments(attachments, no_attachment_links) |> maybe_add_attachments(attachments, attachment_links)
|> maybe_add_nsfw_tag(data) |> maybe_add_nsfw_tag(data)
end end
@ -270,7 +270,7 @@ def make_context(_, %Participation{} = participation) do
def make_context(%Activity{data: %{"context" => context}}, _), do: context def make_context(%Activity{data: %{"context" => context}}, _), do: context
def make_context(_, _), do: Utils.generate_context_id() def make_context(_, _), do: Utils.generate_context_id()
def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed def maybe_add_attachments(parsed, _attachments, false = _no_links), do: parsed
def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do def maybe_add_attachments({text, mentions, tags}, attachments, _no_links) do
text = add_attachments(text, attachments) text = add_attachments(text, attachments)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ControllerHelper do defmodule Pleroma.Web.ControllerHelper do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Endpoint do defmodule Pleroma.Web.Endpoint do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Federator do defmodule Pleroma.Web.Federator do
@ -15,13 +15,19 @@ defmodule Pleroma.Web.Federator do
require Logger require Logger
@doc "Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161)" @doc """
Returns `true` if the distance to target object does not exceed max configured value.
Serves to prevent fetching of very long threads, especially useful on smaller instances.
Addresses [memory leaks on recursive replies fetching](https://git.pleroma.social/pleroma/pleroma/issues/161).
Applies to fetching of both ancestor (reply-to) and child (reply) objects.
"""
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
def allowed_incoming_reply_depth?(depth) do def allowed_thread_distance?(distance) do
max_replies_depth = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth]) max_distance = Pleroma.Config.get([:instance, :federation_incoming_replies_max_depth])
if max_replies_depth do if max_distance && max_distance >= 0 do
(depth || 1) <= max_replies_depth # Default depth is 0 (an object has zero distance from itself in its thread)
(distance || 0) <= max_distance
else else
true true
end end

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedView do defmodule Pleroma.Web.Feed.FeedView do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.TagController do defmodule Pleroma.Web.Feed.TagController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.UserController do defmodule Pleroma.Web.Feed.UserController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastoFEController do defmodule Pleroma.Web.MastoFEController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationController do defmodule Pleroma.Web.MastodonAPI.NotificationController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SearchController do defmodule Pleroma.Web.MastodonAPI.SearchController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.StatusController do defmodule Pleroma.Web.MastodonAPI.StatusController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SubscriptionController do defmodule Pleroma.Web.MastodonAPI.SubscriptionController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.SuggestionController do defmodule Pleroma.Web.MastodonAPI.SuggestionController do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.TimelineController do defmodule Pleroma.Web.MastodonAPI.TimelineController do
@ -10,9 +10,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
# TODO: Replace with a macro when there is a Phoenix release with
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
# in it
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
plug(RateLimiter, [name: :timeline, bucket_name: :home_timeline] when action == :home)
plug(RateLimiter, [name: :timeline, bucket_name: :hashtag_timeline] when action == :hashtag)
plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list)

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do defmodule Pleroma.Web.MastodonAPI.MastodonAPI do

View file

@ -1,5 +1,5 @@
# Pleroma: A lightweight social networking server # Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AccountView do defmodule Pleroma.Web.MastodonAPI.AccountView do

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