mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2025-03-13 07:02:41 +00:00
Merge remote-tracking branch 'origin/develop' into post-languages
This commit is contained in:
commit
ea01b5934f
184 changed files with 3349 additions and 1197 deletions
|
@ -1,8 +1,8 @@
|
||||||
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-25
|
image: git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.14.5-otp-25
|
||||||
|
|
||||||
variables: &global_variables
|
variables: &global_variables
|
||||||
# Only used for the release
|
# Only used for the release
|
||||||
ELIXIR_VER: 1.13.4
|
ELIXIR_VER: 1.17.3
|
||||||
POSTGRES_DB: pleroma_test
|
POSTGRES_DB: pleroma_test
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
@ -71,7 +71,7 @@ check-changelog:
|
||||||
tags:
|
tags:
|
||||||
- amd64
|
- amd64
|
||||||
|
|
||||||
build-1.13.4-otp-25:
|
build-1.14.5-otp-25:
|
||||||
extends:
|
extends:
|
||||||
- .build_changes_policy
|
- .build_changes_policy
|
||||||
- .using-ci-base
|
- .using-ci-base
|
||||||
|
@ -119,7 +119,7 @@ benchmark:
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix pleroma.load_testing
|
- mix pleroma.load_testing
|
||||||
|
|
||||||
unit-testing-1.13.4-otp-25:
|
unit-testing-1.14.5-otp-25:
|
||||||
extends:
|
extends:
|
||||||
- .build_changes_policy
|
- .build_changes_policy
|
||||||
- .using-ci-base
|
- .using-ci-base
|
||||||
|
@ -134,7 +134,7 @@ unit-testing-1.13.4-otp-25:
|
||||||
script: &testing_script
|
script: &testing_script
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mix test --cover --preload-modules
|
- mix pleroma.test_runner --cover --preload-modules
|
||||||
coverage: '/^Line total: ([^ ]*%)$/'
|
coverage: '/^Line total: ([^ ]*%)$/'
|
||||||
artifacts:
|
artifacts:
|
||||||
reports:
|
reports:
|
||||||
|
@ -272,7 +272,8 @@ stop_review_app:
|
||||||
|
|
||||||
amd64:
|
amd64:
|
||||||
stage: release
|
stage: release
|
||||||
image: elixir:$ELIXIR_VER
|
image:
|
||||||
|
name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011
|
||||||
only: &release-only
|
only: &release-only
|
||||||
- stable@pleroma/pleroma
|
- stable@pleroma/pleroma
|
||||||
- develop@pleroma/pleroma
|
- develop@pleroma/pleroma
|
||||||
|
@ -297,8 +298,9 @@ amd64:
|
||||||
variables: &release-variables
|
variables: &release-variables
|
||||||
MIX_ENV: prod
|
MIX_ENV: prod
|
||||||
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
VIX_COMPILATION_MODE: PLATFORM_PROVIDED_LIBVIPS
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
before_script: &before-release
|
before_script: &before-release
|
||||||
- apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev
|
- apt-get update && apt-get install -y cmake libmagic-dev libvips-dev erlang-dev git
|
||||||
- echo "import Config" > config/prod.secret.exs
|
- echo "import Config" > config/prod.secret.exs
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
|
@ -313,7 +315,8 @@ amd64-musl:
|
||||||
stage: release
|
stage: release
|
||||||
artifacts: *release-artifacts
|
artifacts: *release-artifacts
|
||||||
only: *release-only
|
only: *release-only
|
||||||
image: elixir:$ELIXIR_VER-alpine
|
image:
|
||||||
|
name: hexpm/elixir-amd64:1.17.3-erlang-26.2.5.6-alpine-3.17.9
|
||||||
tags:
|
tags:
|
||||||
- amd64
|
- amd64
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
|
@ -327,6 +330,7 @@ amd64-musl:
|
||||||
|
|
||||||
arm:
|
arm:
|
||||||
stage: release
|
stage: release
|
||||||
|
allow_failure: true
|
||||||
artifacts: *release-artifacts
|
artifacts: *release-artifacts
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
|
@ -355,7 +359,8 @@ arm64:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm
|
- arm
|
||||||
image: arm64v8/elixir:$ELIXIR_VER
|
image:
|
||||||
|
name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-ubuntu-focal-20241011
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release
|
before_script: *before-release
|
||||||
|
@ -367,7 +372,8 @@ arm64-musl:
|
||||||
only: *release-only
|
only: *release-only
|
||||||
tags:
|
tags:
|
||||||
- arm
|
- arm
|
||||||
image: arm64v8/elixir:$ELIXIR_VER-alpine
|
image:
|
||||||
|
name: hexpm/elixir-arm64:1.17.3-erlang-26.2.5.6-alpine-3.17.9
|
||||||
cache: *release-cache
|
cache: *release-cache
|
||||||
variables: *release-variables
|
variables: *release-variables
|
||||||
before_script: *before-release-musl
|
before_script: *before-release-musl
|
||||||
|
|
71
CHANGELOG.md
71
CHANGELOG.md
|
@ -4,6 +4,77 @@ 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/).
|
||||||
|
|
||||||
|
## 2.8.0
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Metadata: Do not include .atom feed links for remote accounts
|
||||||
|
- Bumped `fast_html` to v2.3.0, which notably allows to use system-installed lexbor with passing `WITH_SYSTEM_LEXBOR=1` environment variable at build-time
|
||||||
|
- Dedupe upload filter now uses a three-level sharding directory structure
|
||||||
|
- Deprecate `/api/v1/pleroma/accounts/:id/subscribe`/`unsubscribe`
|
||||||
|
- Restrict incoming activities from unknown actors to a subset that does not imply a previous relationship and early rejection of unrecognized activity types.
|
||||||
|
- Elixir 1.14 and Erlang/OTP 23 is now the minimum supported release
|
||||||
|
- Support `id` param in `GET /api/v1/statuses`
|
||||||
|
- LDAP authentication has been refactored to operate as a GenServer process which will maintain an active connection to the LDAP server.
|
||||||
|
- Fix 'Setting a marker should mark notifications as read'
|
||||||
|
- Adjust more Oban workers to enforce unique job constraints.
|
||||||
|
- Oban updated to 2.18.3
|
||||||
|
- Publisher behavior improvement when snoozing Oban jobs due to Gun connection pool contention.
|
||||||
|
- Poll results refreshing is handled asynchronously and will not attempt to keep fetching updates to a closed poll.
|
||||||
|
- Tuning for release builds to lower CPU usage.
|
||||||
|
- Rich Media preview fetching will skip making an HTTP HEAD request to check a URL for allowed content type and length if the Tesla adapter is Gun or Finch
|
||||||
|
- Fix nonexisting user will not generate metadata for search engine opt-out
|
||||||
|
- Update Oban to 2.18
|
||||||
|
- Worker configuration is no longer available. This only affects custom max_retries values for a couple Oban queues.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Add metadata provider for ActivityPub alternate links
|
||||||
|
- Added support for argon2 passwords and their conversion for migration from Akkoma fork to upstream.
|
||||||
|
- Respect :restrict_unauthenticated for hashtag rss/atom feeds
|
||||||
|
- LDAP configuration now permits overriding the CA root certificate file for TLS validation.
|
||||||
|
- LDAP now supports users changing their passwords
|
||||||
|
- Include list id in StatusView
|
||||||
|
- Added MRF.FODirectReply which changes replies to followers-only posts to be direct.
|
||||||
|
- Add `id_filter` to MRF to filter URLs and their domain prior to fetching
|
||||||
|
- Added MRF.QuietReply which prevents replies to public posts from being published to the timelines
|
||||||
|
- Add `group_key` to notifications
|
||||||
|
- Allow providing avatar/header descriptions
|
||||||
|
- Added RemoteReportPolicy from Rebased for handling bogus federated reports
|
||||||
|
- scrubbers/default: Allow "mention hashtag" classes used by Mastodon
|
||||||
|
- Added dependencies for Swoosh's Mua mail adapter
|
||||||
|
- Include session scopes in TokenView
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Verify a local Update sent through AP C2S so users can only update their own objects
|
||||||
|
- Fixed malformed follow requests that cause them to appear stuck pending due to the recipient being unable to process them.
|
||||||
|
- Fix incoming Block activities being rejected
|
||||||
|
- STARTTLS certificate and hostname verification for LDAP authentication
|
||||||
|
- LDAPS connections (implicit TLS) are now supported.
|
||||||
|
- Fix /api/v2/media returning the wrong status code (202) for media processed synchronously
|
||||||
|
- Miscellaneous fixes for Meilisearch support
|
||||||
|
- Fix pleroma_ctl mix task calls sometimes not being found
|
||||||
|
- Add a rate limiter to the OAuth App creation endpoint and ensure registered apps are assigned to users.
|
||||||
|
- ReceiverWorker will cancel processing jobs instead of retrying if the user cannot be fetched due to 403, 404, or 410 errors or if the account is disabled locally.
|
||||||
|
- Address case where instance reachability status couldn't be updated
|
||||||
|
- Remote Fetcher Worker recognizes more permanent failure errors
|
||||||
|
- StreamerView: Do not leak follows count if hidden
|
||||||
|
- Imports of blocks, mutes, and follows would retry repeatedly due to incorrect error handling and all work executed in a single job
|
||||||
|
- Make vapid_config return empty array, fixing preloading for instances without push notifications configured
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Remove stub for /api/v1/accounts/:id/identity_proofs (deprecated by Mastodon 3.5.0)
|
||||||
|
|
||||||
|
## 2.7.1
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Accept `application/activity+json` for requests to `/.well-known/nodeinfo`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Truncate remote user fields, avoids them getting rejected
|
||||||
|
- Improve the `FollowValidator` to successfully incoming activities with an errant `cc` field.
|
||||||
|
- Resolved edge case where the API can report you are following a user but the relationship is not fully established.
|
||||||
|
- The Swoosh email adapter for Mailgun was missing a new dependency on `:multipart`
|
||||||
|
- Fix Mastodon WebSocket authentication
|
||||||
|
|
||||||
## 2.7.0
|
## 2.7.0
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
# https://hub.docker.com/r/hexpm/elixir/tags
|
||||||
ARG ELIXIR_IMG=hexpm/elixir
|
ARG ELIXIR_IMG=hexpm/elixir
|
||||||
ARG ELIXIR_VER=1.13.4
|
ARG ELIXIR_VER=1.14.5
|
||||||
ARG ERLANG_VER=24.3.4.15
|
ARG ERLANG_VER=25.3.2.14
|
||||||
ARG ALPINE_VER=3.17.5
|
ARG ALPINE_VER=3.17.9
|
||||||
|
|
||||||
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
|
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build
|
||||||
|
|
||||||
|
|
1
changelog.d/301-small-image-redirect.change
Normal file
1
changelog.d/301-small-image-redirect.change
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Performance: Use 301 (permanent) redirect instead of 302 (temporary) when redirecting small images in media proxy. This allows browsers to cache the redirect response.
|
1
changelog.d/actor-published-date.add
Normal file
1
changelog.d/actor-published-date.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Include "published" in actor view
|
1
changelog.d/backup-links.add
Normal file
1
changelog.d/backup-links.add
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Link to exported outbox/followers/following collections in backup actor.json
|
|
@ -1 +0,0 @@
|
||||||
Truncate remote user fields, avoids them getting rejected
|
|
|
@ -1 +0,0 @@
|
||||||
Deprecate `/api/v1/pleroma/accounts/:id/subscribe`/`unsubscribe`
|
|
1
changelog.d/fix-mastodon-edits.fix
Normal file
1
changelog.d/fix-mastodon-edits.fix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix Mastodon incoming edits with inlined "likes"
|
|
@ -1 +0,0 @@
|
||||||
Fixed malformed follow requests that cause them to appear stuck pending due to the recipient being unable to process them.
|
|
|
@ -1 +0,0 @@
|
||||||
Improve the FollowValidator to successfully incoming activities with an errant cc field.
|
|
|
@ -1 +0,0 @@
|
||||||
Support `id` param in `GET /api/v1/statuses`
|
|
|
@ -1 +0,0 @@
|
||||||
Remove stub for /api/v1/accounts/:id/identity_proofs (deprecated by Mastodon 3.5.0)
|
|
|
@ -1 +0,0 @@
|
||||||
The Swoosh email adapter for Mailgun was missing a new dependency on :multipart
|
|
|
@ -1 +0,0 @@
|
||||||
Added MRF.FODirectReply which changes replies to followers-only posts to be direct.
|
|
|
@ -1 +0,0 @@
|
||||||
Added MRF.QuietReply which prevents replies to public posts from being published to the timelines
|
|
|
@ -1 +0,0 @@
|
||||||
Fix 'Setting a marker should mark notifications as read'
|
|
|
@ -1 +0,0 @@
|
||||||
Publisher behavior improvement when snoozing Oban jobs due to Gun connection pool contention.
|
|
|
@ -1 +0,0 @@
|
||||||
Address case where instance reachability status couldn't be updated
|
|
|
@ -1 +0,0 @@
|
||||||
Remote Fetcher Worker recognizes more permanent failure errors
|
|
|
@ -1 +0,0 @@
|
||||||
StreamerView: Do not leak follows count if hidden
|
|
|
@ -1 +0,0 @@
|
||||||
Update Oban to 2.18
|
|
1
changelog.d/vips-blurhash.fix
Normal file
1
changelog.d/vips-blurhash.fix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Fix blurhash generation crashes
|
|
@ -1 +0,0 @@
|
||||||
Worker configuration is no longer available. This only affects custom max_retries values for a couple Oban queues.
|
|
|
@ -1 +0,0 @@
|
||||||
docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.12 --push .
|
|
|
@ -1,8 +0,0 @@
|
||||||
FROM elixir:1.13.4-otp-25
|
|
||||||
|
|
||||||
# Single RUN statement, otherwise intermediate images are created
|
|
||||||
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
|
|
||||||
RUN apt-get update &&\
|
|
||||||
apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
|
|
||||||
mix local.hex --force &&\
|
|
||||||
mix local.rebar --force
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM elixir:1.12.3
|
FROM elixir:1.14.5-otp-25
|
||||||
|
|
||||||
# Single RUN statement, otherwise intermediate images are created
|
# Single RUN statement, otherwise intermediate images are created
|
||||||
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
|
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run
|
|
@ -1 +1 @@
|
||||||
docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.13.4-otp-25 --push .
|
docker buildx build --platform linux/amd64,linux/arm64 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:elixir-1.14.5-otp-25 --push .
|
|
@ -344,7 +344,7 @@ config :pleroma, :manifest,
|
||||||
icons: [
|
icons: [
|
||||||
%{
|
%{
|
||||||
src: "/static/logo.svg",
|
src: "/static/logo.svg",
|
||||||
sizes: "144x144",
|
sizes: "512x512",
|
||||||
purpose: "any",
|
purpose: "any",
|
||||||
type: "image/svg+xml"
|
type: "image/svg+xml"
|
||||||
}
|
}
|
||||||
|
@ -434,6 +434,11 @@ config :pleroma, :mrf_follow_bot, follower_nickname: nil
|
||||||
|
|
||||||
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
|
config :pleroma, :mrf_inline_quote, template: "<bdi>RT:</bdi> {url}"
|
||||||
|
|
||||||
|
config :pleroma, :mrf_remote_report,
|
||||||
|
reject_all: false,
|
||||||
|
reject_anonymous: true,
|
||||||
|
reject_empty_message: true
|
||||||
|
|
||||||
config :pleroma, :mrf_force_mention,
|
config :pleroma, :mrf_force_mention,
|
||||||
mention_parent: true,
|
mention_parent: true,
|
||||||
mention_quoted: true
|
mention_quoted: true
|
||||||
|
@ -597,7 +602,8 @@ config :pleroma, Oban,
|
||||||
plugins: [{Oban.Plugins.Pruner, max_age: 900}],
|
plugins: [{Oban.Plugins.Pruner, max_age: 900}],
|
||||||
crontab: [
|
crontab: [
|
||||||
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
{"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker},
|
||||||
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}
|
{"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker},
|
||||||
|
{"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :pleroma, Pleroma.Formatter,
|
config :pleroma, Pleroma.Formatter,
|
||||||
|
@ -611,14 +617,17 @@ config :pleroma, Pleroma.Formatter,
|
||||||
|
|
||||||
config :pleroma, :ldap,
|
config :pleroma, :ldap,
|
||||||
enabled: System.get_env("LDAP_ENABLED") == "true",
|
enabled: System.get_env("LDAP_ENABLED") == "true",
|
||||||
host: System.get_env("LDAP_HOST") || "localhost",
|
host: System.get_env("LDAP_HOST", "localhost"),
|
||||||
port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
|
port: String.to_integer(System.get_env("LDAP_PORT", "389")),
|
||||||
ssl: System.get_env("LDAP_SSL") == "true",
|
ssl: System.get_env("LDAP_SSL") == "true",
|
||||||
sslopts: [],
|
sslopts: [],
|
||||||
tls: System.get_env("LDAP_TLS") == "true",
|
tls: System.get_env("LDAP_TLS") == "true",
|
||||||
tlsopts: [],
|
tlsopts: [],
|
||||||
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
|
base: System.get_env("LDAP_BASE", "dc=example,dc=com"),
|
||||||
uid: System.get_env("LDAP_UID") || "cn"
|
uid: System.get_env("LDAP_UID", "cn"),
|
||||||
|
# defaults to CAStore's Mozilla roots
|
||||||
|
cacertfile: System.get_env("LDAP_CACERTFILE", nil),
|
||||||
|
mail: System.get_env("LDAP_MAIL", "mail")
|
||||||
|
|
||||||
oauth_consumer_strategies =
|
oauth_consumer_strategies =
|
||||||
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|
System.get_env("OAUTH_CONSUMER_STRATEGIES")
|
||||||
|
@ -711,6 +720,7 @@ config :pleroma, :rate_limit,
|
||||||
timeline: {500, 3},
|
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},
|
||||||
|
oauth_app_creation: {900_000, 5},
|
||||||
relations_actions: {10_000, 10},
|
relations_actions: {10_000, 10},
|
||||||
relation_id_action: {60_000, 2},
|
relation_id_action: {60_000, 2},
|
||||||
statuses_actions: {10_000, 15},
|
statuses_actions: {10_000, 15},
|
||||||
|
|
|
@ -2241,14 +2241,8 @@ config :pleroma, :config_description, [
|
||||||
label: "SSL options",
|
label: "SSL options",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "Additional SSL options",
|
description: "Additional SSL options",
|
||||||
suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
|
suggestions: [verify: :verify_peer],
|
||||||
children: [
|
children: [
|
||||||
%{
|
|
||||||
key: :cacertfile,
|
|
||||||
type: :string,
|
|
||||||
description: "Path to file with PEM encoded cacerts",
|
|
||||||
suggestions: ["path/to/file/with/PEM/cacerts"]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :verify,
|
key: :verify,
|
||||||
type: :atom,
|
type: :atom,
|
||||||
|
@ -2268,14 +2262,8 @@ config :pleroma, :config_description, [
|
||||||
label: "TLS options",
|
label: "TLS options",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "Additional TLS options",
|
description: "Additional TLS options",
|
||||||
suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
|
suggestions: [verify: :verify_peer],
|
||||||
children: [
|
children: [
|
||||||
%{
|
|
||||||
key: :cacertfile,
|
|
||||||
type: :string,
|
|
||||||
description: "Path to file with PEM encoded cacerts",
|
|
||||||
suggestions: ["path/to/file/with/PEM/cacerts"]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :verify,
|
key: :verify,
|
||||||
type: :atom,
|
type: :atom,
|
||||||
|
@ -2292,11 +2280,25 @@ config :pleroma, :config_description, [
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :uid,
|
key: :uid,
|
||||||
label: "UID",
|
label: "UID Attribute",
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
|
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
|
||||||
suggestions: ["cn"]
|
suggestions: ["cn"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :cacertfile,
|
||||||
|
label: "CACertfile",
|
||||||
|
type: :string,
|
||||||
|
description: "Path to CA certificate file"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :mail,
|
||||||
|
label: "Mail Attribute",
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"LDAP attribute name to use as the email address when automatically registering the user on first login",
|
||||||
|
suggestions: ["mail"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -3300,8 +3302,7 @@ config :pleroma, :config_description, [
|
||||||
suggestions: [
|
suggestions: [
|
||||||
Pleroma.Web.Preload.Providers.Instance,
|
Pleroma.Web.Preload.Providers.Instance,
|
||||||
Pleroma.Web.Preload.Providers.User,
|
Pleroma.Web.Preload.Providers.User,
|
||||||
Pleroma.Web.Preload.Providers.Timelines,
|
Pleroma.Web.Preload.Providers.Timelines
|
||||||
Pleroma.Web.Preload.Providers.StatusNet
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -742,6 +742,21 @@ config :pleroma, Pleroma.Emails.Mailer,
|
||||||
auth: :always
|
auth: :always
|
||||||
```
|
```
|
||||||
|
|
||||||
|
An example for Mua adapter:
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :pleroma, Pleroma.Emails.Mailer,
|
||||||
|
enabled: true,
|
||||||
|
adapter: Swoosh.Adapters.Mua,
|
||||||
|
relay: "mail.example.com",
|
||||||
|
port: 465,
|
||||||
|
auth: [
|
||||||
|
username: "YOUR_USERNAME@domain.tld",
|
||||||
|
password: "YOUR_SMTP_PASSWORD"
|
||||||
|
],
|
||||||
|
protocol: :ssl
|
||||||
|
```
|
||||||
|
|
||||||
### :email_notifications
|
### :email_notifications
|
||||||
|
|
||||||
Email notifications settings.
|
Email notifications settings.
|
||||||
|
@ -968,12 +983,13 @@ Pleroma account will be created with the same name as the LDAP user name.
|
||||||
* `enabled`: enables LDAP authentication
|
* `enabled`: enables LDAP authentication
|
||||||
* `host`: LDAP server hostname
|
* `host`: LDAP server hostname
|
||||||
* `port`: LDAP port, e.g. 389 or 636
|
* `port`: LDAP port, e.g. 389 or 636
|
||||||
* `ssl`: true to use SSL, usually implies the port 636
|
* `ssl`: true to use implicit SSL/TLS, usually port 636
|
||||||
* `sslopts`: additional SSL options
|
* `sslopts`: additional SSL options
|
||||||
* `tls`: true to start TLS, usually implies the port 389
|
* `tls`: true to use explicit TLS (STARTTLS), usually port 389
|
||||||
* `tlsopts`: additional TLS options
|
* `tlsopts`: additional TLS options
|
||||||
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
* `base`: LDAP base, e.g. "dc=example,dc=com"
|
||||||
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
* `uid`: LDAP attribute name to authenticate the user, e.g. when "cn", the filter will be "cn=username,base"
|
||||||
|
* `cacertfile`: Path to alternate CA root certificates file
|
||||||
|
|
||||||
Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an
|
Note, if your LDAP server is an Active Directory server the correct value is commonly `uid: "cn"`, but if you use an
|
||||||
OpenLDAP server the value may be `uid: "uid"`.
|
OpenLDAP server the value may be `uid: "uid"`.
|
||||||
|
|
|
@ -433,7 +433,7 @@ Response:
|
||||||
* On success: URL of the unfollowed relay
|
* On success: URL of the unfollowed relay
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"https://example.com/relay"}
|
"https://example.com/relay"
|
||||||
```
|
```
|
||||||
|
|
||||||
## `POST /api/v1/pleroma/admin/users/invite_token`
|
## `POST /api/v1/pleroma/admin/users/invite_token`
|
||||||
|
@ -1193,7 +1193,8 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
|
"items": [
|
||||||
{
|
{
|
||||||
"id": 1234,
|
"id": 1234,
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -1206,7 +1207,9 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
"time": 1502812026, // timestamp
|
"time": 1502812026, // timestamp
|
||||||
"message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message
|
"message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"total": 1
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `POST /api/v1/pleroma/admin/reload_emoji`
|
## `POST /api/v1/pleroma/admin/reload_emoji`
|
||||||
|
|
|
@ -42,6 +42,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `quotes_count`: the count of status quotes.
|
- `quotes_count`: the count of status quotes.
|
||||||
- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen.
|
- `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen.
|
||||||
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
|
- `bookmark_folder`: the ID of the folder bookmark is stored within (if any).
|
||||||
|
- `list_id`: the ID of the list the post is addressed to (if any, only returned to author).
|
||||||
|
|
||||||
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
|
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
|
||||||
|
|
||||||
|
@ -120,6 +121,8 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
- `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned.
|
||||||
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
- `accepts_chat_messages`: boolean, but can be null if we don't have that information about a user
|
||||||
- `favicon`: nullable URL string, Favicon image of the user's instance
|
- `favicon`: nullable URL string, Favicon image of the user's instance
|
||||||
|
- `avatar_description`: string, image description for user avatar, defaults to empty string
|
||||||
|
- `header_description`: string, image description for user banner, defaults to empty string
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
|
@ -255,6 +258,8 @@ Additional parameters can be added to the JSON body/Form data:
|
||||||
- `actor_type` - the type of this account.
|
- `actor_type` - the type of this account.
|
||||||
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
- `accepts_chat_messages` - if false, this account will reject all chat messages.
|
||||||
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
|
- `language` - user's preferred language for receiving emails (digest, confirmation, etc.)
|
||||||
|
- `avatar_description` - image description for user avatar
|
||||||
|
- `header_description` - image description for user banner
|
||||||
|
|
||||||
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
All images (avatar, banner and background) can be reset to the default by sending an empty string ("") instead of a file.
|
||||||
|
|
||||||
|
|
|
@ -69,12 +69,18 @@ cd /opt/pleroma
|
||||||
sudo -Hu pleroma mix deps.get
|
sudo -Hu pleroma mix deps.get
|
||||||
```
|
```
|
||||||
|
|
||||||
* Generate the configuration: `sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
|
* Generate the configuration:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo -Hu pleroma MIX_ENV=prod mix pleroma.instance gen`
|
||||||
|
```
|
||||||
|
|
||||||
|
* During this process:
|
||||||
* Answer with `yes` if it asks you to install `rebar3`.
|
* Answer with `yes` if it asks you to install `rebar3`.
|
||||||
* This may take some time, because parts of pleroma get compiled first.
|
* This may take some time, because parts of pleroma get compiled first.
|
||||||
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
* After that it will ask you a few questions about your instance and generates a configuration file in `config/generated_config.exs`.
|
||||||
|
|
||||||
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for productive instance, `dev.secret.exs` for development instances):
|
* Check the configuration and if all looks right, rename it, so Pleroma will load it (`prod.secret.exs` for production instances, `dev.secret.exs` for development instances):
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
|
sudo -Hu pleroma mv config/{generated_config.exs,prod.secret.exs}
|
||||||
|
|
|
@ -14,7 +14,7 @@ Note: This article is potentially outdated because at this time we may not have
|
||||||
|
|
||||||
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
- PostgreSQL 11.0以上 (Ubuntu16.04では9.5しか提供されていないので,[](https://www.postgresql.org/download/linux/ubuntu/)こちらから新しいバージョンを入手してください)
|
||||||
- `postgresql-contrib` 11.0以上 (同上)
|
- `postgresql-contrib` 11.0以上 (同上)
|
||||||
- Elixir 1.13 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
- Elixir 1.14 以上 ([Debianのリポジトリからインストールしないこと!!! ここからインストールすること!](https://elixir-lang.org/install.html#unix-and-unix-like)。または [asdf](https://github.com/asdf-vm/asdf) をpleromaユーザーでインストールしてください)
|
||||||
- `erlang-dev`
|
- `erlang-dev`
|
||||||
- `erlang-nox`
|
- `erlang-nox`
|
||||||
- `git`
|
- `git`
|
||||||
|
|
|
@ -31,7 +31,7 @@ Setup the required services to automatically start at boot, using `sysrc(8)`.
|
||||||
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
|
### Install media / graphics packages (optional, see [`docs/installation/optional/media_graphics_packages.md`](../installation/optional/media_graphics_packages.md))
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# pkg install imagemagick ffmpeg p5-Image-ExifTool
|
# pkg install imagemagick ffmpeg p5-Image-ExifTool vips
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuring Pleroma
|
## Configuring Pleroma
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
## Required dependencies
|
## Required dependencies
|
||||||
|
|
||||||
* PostgreSQL >=11.0
|
* PostgreSQL >=11.0
|
||||||
* Elixir >=1.13.0 <1.17
|
* Elixir >=1.14.0 <1.17
|
||||||
* Erlang OTP >=22.2.0 (supported: <27)
|
* Erlang OTP >=23.0.0 (supported: <27)
|
||||||
* git
|
* git
|
||||||
* file / libmagic
|
* file / libmagic
|
||||||
* gcc or clang
|
* gcc or clang
|
||||||
|
|
|
@ -12,7 +12,7 @@ For any additional information regarding commands and configuration files mentio
|
||||||
To install them, run the following command (with doas or as root):
|
To install them, run the following command (with doas or as root):
|
||||||
|
|
||||||
```
|
```
|
||||||
pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick
|
pkg_add elixir gmake git postgresql-server postgresql-contrib cmake ffmpeg ImageMagick libvips
|
||||||
```
|
```
|
||||||
|
|
||||||
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
Pleroma requires a reverse proxy, OpenBSD has relayd in base (and is used in this guide) and packages/ports are available for nginx (www/nginx) and apache (www/apache-httpd). Independently of the reverse proxy, [acme-client(1)](https://man.openbsd.org/acme-client) can be used to get a certificate from Let's Encrypt.
|
||||||
|
|
|
@ -18,7 +18,7 @@ Matrix-kanava #pleroma:libera.chat ovat hyviä paikkoja löytää apua
|
||||||
|
|
||||||
Asenna tarvittava ohjelmisto:
|
Asenna tarvittava ohjelmisto:
|
||||||
|
|
||||||
`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake ffmpeg ImageMagick`
|
`# pkg_add git elixir gmake postgresql-server-10.3 postgresql-contrib-10.3 cmake ffmpeg ImageMagick libvips`
|
||||||
|
|
||||||
#### Optional software
|
#### Optional software
|
||||||
|
|
||||||
|
|
7
installation/openldap/pw_self_service.ldif
Normal file
7
installation/openldap/pw_self_service.ldif
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
dn: olcDatabase={1}mdb,cn=config
|
||||||
|
changetype: modify
|
||||||
|
add: olcAccess
|
||||||
|
olcAccess: {1}to attrs=userPassword
|
||||||
|
by self write
|
||||||
|
by anonymous auth
|
||||||
|
by * none
|
|
@ -9,7 +9,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
import Pleroma.Search.Meilisearch,
|
import Pleroma.Search.Meilisearch,
|
||||||
only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete: 1]
|
only: [meili_put: 2, meili_get: 1, meili_delete: 1]
|
||||||
|
|
||||||
def run(["index"]) do
|
def run(["index"]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
@ -28,7 +28,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
meili_post(
|
meili_put(
|
||||||
"/indexes/objects/settings/ranking-rules",
|
"/indexes/objects/settings/ranking-rules",
|
||||||
[
|
[
|
||||||
"published:desc",
|
"published:desc",
|
||||||
|
@ -42,7 +42,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
|
||||||
)
|
)
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
meili_post(
|
meili_put(
|
||||||
"/indexes/objects/settings/searchable-attributes",
|
"/indexes/objects/settings/searchable-attributes",
|
||||||
[
|
[
|
||||||
"content"
|
"content"
|
||||||
|
|
25
lib/mix/tasks/pleroma/test_runner.ex
Normal file
25
lib/mix/tasks/pleroma/test_runner.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule Mix.Tasks.Pleroma.TestRunner do
|
||||||
|
@shortdoc "Retries tests once if they fail"
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
def run(args \\ []) do
|
||||||
|
case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do
|
||||||
|
{_, 0} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
retry(args)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def retry(args) do
|
||||||
|
case System.cmd("mix", ["test", "--failed"] ++ args, into: IO.stream(:stdio, :line)) do
|
||||||
|
{_, 0} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -94,6 +94,7 @@ defmodule Pleroma.Application do
|
||||||
children =
|
children =
|
||||||
[
|
[
|
||||||
Pleroma.PromEx,
|
Pleroma.PromEx,
|
||||||
|
Pleroma.LDAP,
|
||||||
Pleroma.Repo,
|
Pleroma.Repo,
|
||||||
Config.TransferTask,
|
Config.TransferTask,
|
||||||
Pleroma.Emoji,
|
Pleroma.Emoji,
|
||||||
|
|
|
@ -22,7 +22,8 @@ defmodule Pleroma.Config.TransferTask do
|
||||||
{:pleroma, :markup},
|
{:pleroma, :markup},
|
||||||
{:pleroma, :streamer},
|
{:pleroma, :streamer},
|
||||||
{:pleroma, :pools},
|
{:pleroma, :pools},
|
||||||
{:pleroma, :connections_pool}
|
{:pleroma, :connections_pool},
|
||||||
|
{:pleroma, :ldap}
|
||||||
]
|
]
|
||||||
|
|
||||||
defp reboot_time_subkeys,
|
defp reboot_time_subkeys,
|
||||||
|
|
|
@ -87,6 +87,41 @@ defmodule Pleroma.Constants do
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const(activity_types,
|
||||||
|
do: [
|
||||||
|
"Block",
|
||||||
|
"Create",
|
||||||
|
"Update",
|
||||||
|
"Delete",
|
||||||
|
"Follow",
|
||||||
|
"Accept",
|
||||||
|
"Reject",
|
||||||
|
"Add",
|
||||||
|
"Remove",
|
||||||
|
"Like",
|
||||||
|
"Announce",
|
||||||
|
"Undo",
|
||||||
|
"Flag",
|
||||||
|
"EmojiReact"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
const(allowed_activity_types_from_strangers,
|
||||||
|
do: [
|
||||||
|
"Block",
|
||||||
|
"Create",
|
||||||
|
"Flag",
|
||||||
|
"Follow",
|
||||||
|
"Like",
|
||||||
|
"EmojiReact",
|
||||||
|
"Announce"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
const(object_types,
|
||||||
|
do: ~w[Event Question Answer Audio Video Image Article Note Page ChatMessage]
|
||||||
|
)
|
||||||
|
|
||||||
# basic regex, just there to weed out potential mistakes
|
# basic regex, just there to weed out potential mistakes
|
||||||
# https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
|
# https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
|
||||||
const(mime_regex,
|
const(mime_regex,
|
||||||
|
|
|
@ -74,11 +74,14 @@ defmodule Pleroma.Frontend do
|
||||||
|
|
||||||
new_file_path = Path.join(dest, path)
|
new_file_path = Path.join(dest, path)
|
||||||
|
|
||||||
new_file_path
|
path
|
||||||
|> Path.dirname()
|
|> Path.dirname()
|
||||||
|
|> then(&Path.join(dest, &1))
|
||||||
|> File.mkdir_p!()
|
|> File.mkdir_p!()
|
||||||
|
|
||||||
|
if not File.dir?(new_file_path) do
|
||||||
File.write!(new_file_path, data)
|
File.write!(new_file_path, data)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -52,6 +52,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
||||||
case adapter() do
|
case adapter() do
|
||||||
Tesla.Adapter.Gun -> AdapterHelper.Gun
|
Tesla.Adapter.Gun -> AdapterHelper.Gun
|
||||||
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
|
Tesla.Adapter.Hackney -> AdapterHelper.Hackney
|
||||||
|
{Tesla.Adapter.Finch, _} -> AdapterHelper.Finch
|
||||||
_ -> AdapterHelper.Default
|
_ -> AdapterHelper.Default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -118,4 +119,13 @@ defmodule Pleroma.HTTP.AdapterHelper do
|
||||||
host_charlist
|
host_charlist
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec can_stream? :: bool()
|
||||||
|
def can_stream? do
|
||||||
|
case Application.get_env(:tesla, :adapter) do
|
||||||
|
Tesla.Adapter.Gun -> true
|
||||||
|
{Tesla.Adapter.Finch, _} -> true
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
33
lib/pleroma/http/adapter_helper/finch.ex
Normal file
33
lib/pleroma/http/adapter_helper/finch.ex
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.AdapterHelper.Finch do
|
||||||
|
@behaviour Pleroma.HTTP.AdapterHelper
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.HTTP.AdapterHelper
|
||||||
|
|
||||||
|
@spec options(keyword(), URI.t()) :: keyword()
|
||||||
|
def options(incoming_opts \\ [], %URI{} = _uri) do
|
||||||
|
proxy =
|
||||||
|
[:http, :proxy_url]
|
||||||
|
|> Config.get()
|
||||||
|
|> AdapterHelper.format_proxy()
|
||||||
|
|
||||||
|
config_opts = Config.get([:http, :adapter], [])
|
||||||
|
|
||||||
|
config_opts
|
||||||
|
|> Keyword.merge(incoming_opts)
|
||||||
|
|> AdapterHelper.maybe_add_proxy(proxy)
|
||||||
|
|> maybe_stream()
|
||||||
|
end
|
||||||
|
|
||||||
|
# Finch uses [response: :stream]
|
||||||
|
defp maybe_stream(opts) do
|
||||||
|
case Keyword.pop(opts, :stream, nil) do
|
||||||
|
{true, opts} -> Keyword.put(opts, :response, :stream)
|
||||||
|
{_, opts} -> opts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -32,6 +32,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
||||||
|> AdapterHelper.maybe_add_proxy(proxy)
|
|> AdapterHelper.maybe_add_proxy(proxy)
|
||||||
|> Keyword.merge(incoming_opts)
|
|> Keyword.merge(incoming_opts)
|
||||||
|> put_timeout()
|
|> put_timeout()
|
||||||
|
|> maybe_stream()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
|
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
|
||||||
|
@ -47,6 +48,14 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do
|
||||||
Keyword.put(opts, :timeout, recv_timeout)
|
Keyword.put(opts, :timeout, recv_timeout)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Gun uses [body_as: :stream]
|
||||||
|
defp maybe_stream(opts) do
|
||||||
|
case Keyword.pop(opts, :stream, nil) do
|
||||||
|
{true, opts} -> Keyword.put(opts, :body_as, :stream)
|
||||||
|
{_, opts} -> opts
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@spec pool_timeout(pool()) :: non_neg_integer()
|
@spec pool_timeout(pool()) :: non_neg_integer()
|
||||||
def pool_timeout(pool) do
|
def pool_timeout(pool) do
|
||||||
default = Config.get([:pools, :default, :recv_timeout], 5_000)
|
default = Config.get([:pools, :default, :recv_timeout], 5_000)
|
||||||
|
|
271
lib/pleroma/ldap.ex
Normal file
271
lib/pleroma/ldap.ex
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
defmodule Pleroma.LDAP do
|
||||||
|
use GenServer
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
import Pleroma.Web.Auth.Helpers, only: [fetch_user: 1]
|
||||||
|
|
||||||
|
@connection_timeout 2_000
|
||||||
|
@search_timeout 2_000
|
||||||
|
|
||||||
|
def start_link(_) do
|
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
def bind_user(name, password) do
|
||||||
|
GenServer.call(__MODULE__, {:bind_user, name, password})
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_password(name, password, new_password) do
|
||||||
|
GenServer.call(__MODULE__, {:change_password, name, password, new_password})
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(state) do
|
||||||
|
case {Config.get(Pleroma.Web.Auth.Authenticator), Config.get([:ldap, :enabled])} do
|
||||||
|
{Pleroma.Web.Auth.LDAPAuthenticator, true} ->
|
||||||
|
{:ok, state, {:continue, :connect}}
|
||||||
|
|
||||||
|
{Pleroma.Web.Auth.LDAPAuthenticator, false} ->
|
||||||
|
Logger.error(
|
||||||
|
"LDAP Authenticator enabled but :pleroma, :ldap is not enabled. Auth will not work."
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, state}
|
||||||
|
|
||||||
|
{_, true} ->
|
||||||
|
Logger.warning(
|
||||||
|
":pleroma, :ldap is enabled but Pleroma.Web.Authenticator is not set to the LDAPAuthenticator. LDAP will not be used."
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, state}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:ok, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_continue(:connect, _state), do: do_handle_connect()
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_info(:connect, _state), do: do_handle_connect()
|
||||||
|
|
||||||
|
def handle_info({:bind_after_reconnect, name, password, from}, state) do
|
||||||
|
result = do_bind_user(state[:handle], name, password)
|
||||||
|
|
||||||
|
GenServer.reply(from, result)
|
||||||
|
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_call({:bind_user, name, password}, from, state) do
|
||||||
|
case do_bind_user(state[:handle], name, password) do
|
||||||
|
:needs_reconnect ->
|
||||||
|
Process.send(self(), {:bind_after_reconnect, name, password, from}, [])
|
||||||
|
{:noreply, state, {:continue, :connect}}
|
||||||
|
|
||||||
|
result ->
|
||||||
|
{:reply, result, state, :hibernate}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_call({:change_password, name, password, new_password}, _from, state) do
|
||||||
|
result = change_password(state[:handle], name, password, new_password)
|
||||||
|
|
||||||
|
{:reply, result, state, :hibernate}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def terminate(_, state) do
|
||||||
|
handle = Keyword.get(state, :handle)
|
||||||
|
|
||||||
|
if not is_nil(handle) do
|
||||||
|
:eldap.close(handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_handle_connect do
|
||||||
|
state =
|
||||||
|
case connect() do
|
||||||
|
{:ok, handle} ->
|
||||||
|
:eldap.controlling_process(handle, self())
|
||||||
|
Process.link(handle)
|
||||||
|
[handle: handle]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Logger.error("Failed to connect to LDAP. Retrying in 5000ms")
|
||||||
|
Process.send_after(self(), :connect, 5_000)
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
{:noreply, state}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp connect do
|
||||||
|
ldap = Config.get(:ldap, [])
|
||||||
|
host = Keyword.get(ldap, :host, "localhost")
|
||||||
|
port = Keyword.get(ldap, :port, 389)
|
||||||
|
ssl = Keyword.get(ldap, :ssl, false)
|
||||||
|
tls = Keyword.get(ldap, :tls, false)
|
||||||
|
cacertfile = Keyword.get(ldap, :cacertfile) || CAStore.file_path()
|
||||||
|
|
||||||
|
if ssl, do: Application.ensure_all_started(:ssl)
|
||||||
|
|
||||||
|
default_secure_opts = [
|
||||||
|
verify: :verify_peer,
|
||||||
|
cacerts: decode_certfile(cacertfile),
|
||||||
|
customize_hostname_check: [
|
||||||
|
fqdn_fun: fn _ -> to_charlist(host) end
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
sslopts = Keyword.merge(default_secure_opts, Keyword.get(ldap, :sslopts, []))
|
||||||
|
tlsopts = Keyword.merge(default_secure_opts, Keyword.get(ldap, :tlsopts, []))
|
||||||
|
|
||||||
|
default_options = [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}]
|
||||||
|
|
||||||
|
# :sslopts can only be included in :eldap.open/2 when {ssl: true}
|
||||||
|
# or the connection will fail
|
||||||
|
options =
|
||||||
|
if ssl do
|
||||||
|
default_options ++ [{:sslopts, sslopts}]
|
||||||
|
else
|
||||||
|
default_options
|
||||||
|
end
|
||||||
|
|
||||||
|
case :eldap.open([to_charlist(host)], options) do
|
||||||
|
{:ok, handle} ->
|
||||||
|
try do
|
||||||
|
cond do
|
||||||
|
tls ->
|
||||||
|
case :eldap.start_tls(
|
||||||
|
handle,
|
||||||
|
tlsopts,
|
||||||
|
@connection_timeout
|
||||||
|
) do
|
||||||
|
:ok ->
|
||||||
|
{:ok, handle}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Logger.error("Could not start TLS: #{inspect(error)}")
|
||||||
|
:eldap.close(handle)
|
||||||
|
end
|
||||||
|
|
||||||
|
true ->
|
||||||
|
{:ok, handle}
|
||||||
|
end
|
||||||
|
after
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, error} ->
|
||||||
|
Logger.error("Could not open LDAP connection: #{inspect(error)}")
|
||||||
|
{:error, {:ldap_connection_error, error}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp do_bind_user(handle, name, password) do
|
||||||
|
dn = make_dn(name)
|
||||||
|
|
||||||
|
case :eldap.simple_bind(handle, dn, password) do
|
||||||
|
:ok ->
|
||||||
|
case fetch_user(name) do
|
||||||
|
%User{} = user ->
|
||||||
|
user
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
register_user(handle, ldap_base(), ldap_uid(), name)
|
||||||
|
end
|
||||||
|
|
||||||
|
# eldap does not inform us of socket closure
|
||||||
|
# until it is used
|
||||||
|
{:error, {:gen_tcp_error, :closed}} ->
|
||||||
|
:eldap.close(handle)
|
||||||
|
:needs_reconnect
|
||||||
|
|
||||||
|
{:error, error} = e ->
|
||||||
|
Logger.error("Could not bind LDAP user #{name}: #{inspect(error)}")
|
||||||
|
e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp register_user(handle, base, uid, name) do
|
||||||
|
case :eldap.search(handle, [
|
||||||
|
{:base, to_charlist(base)},
|
||||||
|
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
||||||
|
{:scope, :eldap.wholeSubtree()},
|
||||||
|
{:timeout, @search_timeout}
|
||||||
|
]) do
|
||||||
|
# The :eldap_search_result record structure changed in OTP 24.3 and added a controls field
|
||||||
|
# https://github.com/erlang/otp/pull/5538
|
||||||
|
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals}} ->
|
||||||
|
try_register(name, attributes)
|
||||||
|
|
||||||
|
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals, _controls}} ->
|
||||||
|
try_register(name, attributes)
|
||||||
|
|
||||||
|
error ->
|
||||||
|
Logger.error("Couldn't register user because LDAP search failed: #{inspect(error)}")
|
||||||
|
{:error, {:ldap_search_error, error}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp try_register(name, attributes) do
|
||||||
|
mail_attribute = Config.get([:ldap, :mail])
|
||||||
|
|
||||||
|
params = %{
|
||||||
|
name: name,
|
||||||
|
nickname: name,
|
||||||
|
password: nil
|
||||||
|
}
|
||||||
|
|
||||||
|
params =
|
||||||
|
case List.keyfind(attributes, to_charlist(mail_attribute), 0) do
|
||||||
|
{_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
|
||||||
|
_ -> params
|
||||||
|
end
|
||||||
|
|
||||||
|
changeset = User.register_changeset_ldap(%User{}, params)
|
||||||
|
|
||||||
|
case User.register(changeset) do
|
||||||
|
{:ok, user} -> user
|
||||||
|
error -> error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp change_password(handle, name, password, new_password) do
|
||||||
|
dn = make_dn(name)
|
||||||
|
|
||||||
|
with :ok <- :eldap.simple_bind(handle, dn, password) do
|
||||||
|
:eldap.modify_password(handle, dn, to_charlist(new_password), to_charlist(password))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode_certfile(file) do
|
||||||
|
with {:ok, data} <- File.read(file) do
|
||||||
|
data
|
||||||
|
|> :public_key.pem_decode()
|
||||||
|
|> Enum.map(fn {_, b, _} -> b end)
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
Logger.error("Unable to read certfile: #{file}")
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp ldap_uid, do: to_charlist(Config.get([:ldap, :uid], "cn"))
|
||||||
|
defp ldap_base, do: to_charlist(Config.get([:ldap, :base]))
|
||||||
|
|
||||||
|
defp make_dn(name) do
|
||||||
|
uid = ldap_uid()
|
||||||
|
base = ldap_base()
|
||||||
|
~c"#{uid}=#{name},#{base}"
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,15 +20,13 @@ defmodule Pleroma.Maps do
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_empty_values(data) do
|
def filter_empty_values(data) do
|
||||||
# TODO: Change to Map.filter in Elixir 1.13+
|
|
||||||
data
|
data
|
||||||
|> Enum.filter(fn
|
|> Map.filter(fn
|
||||||
{_k, nil} -> false
|
{_k, nil} -> false
|
||||||
{_k, ""} -> false
|
{_k, ""} -> false
|
||||||
{_k, []} -> false
|
{_k, []} -> false
|
||||||
{_k, %{} = v} -> Map.keys(v) != []
|
{_k, %{} = v} -> Map.keys(v) != []
|
||||||
{_k, _v} -> true
|
{_k, _v} -> true
|
||||||
end)
|
end)
|
||||||
|> Map.new()
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -99,27 +99,6 @@ defmodule Pleroma.Object do
|
||||||
def get_by_id(nil), do: nil
|
def get_by_id(nil), do: nil
|
||||||
def get_by_id(id), do: Repo.get(Object, id)
|
def get_by_id(id), do: Repo.get(Object, id)
|
||||||
|
|
||||||
@spec get_by_id_and_maybe_refetch(integer(), list()) :: Object.t() | nil
|
|
||||||
def get_by_id_and_maybe_refetch(id, opts \\ []) do
|
|
||||||
with %Object{updated_at: updated_at} = object <- get_by_id(id) do
|
|
||||||
if opts[:interval] &&
|
|
||||||
NaiveDateTime.diff(NaiveDateTime.utc_now(), updated_at) > opts[:interval] do
|
|
||||||
case Fetcher.refetch_object(object) do
|
|
||||||
{:ok, %Object{} = object} ->
|
|
||||||
object
|
|
||||||
|
|
||||||
e ->
|
|
||||||
Logger.error("Couldn't refresh #{object.data["id"]}:\n#{inspect(e)}")
|
|
||||||
object
|
|
||||||
end
|
|
||||||
else
|
|
||||||
object
|
|
||||||
end
|
|
||||||
else
|
|
||||||
nil -> nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_by_ap_id(nil), do: nil
|
def get_by_ap_id(nil), do: nil
|
||||||
|
|
||||||
def get_by_ap_id(ap_id) do
|
def get_by_ap_id(ap_id) do
|
||||||
|
|
|
@ -58,8 +58,12 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@typep fetcher_errors ::
|
||||||
|
:error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier
|
||||||
|
|
||||||
# Note: will create a Create activity, which we need internally at the moment.
|
# Note: will create a Create activity, which we need internally at the moment.
|
||||||
@spec fetch_object_from_id(String.t(), list()) :: {:ok, Object.t()} | {:error | :reject, any()}
|
@spec fetch_object_from_id(String.t(), list()) ::
|
||||||
|
{:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors()
|
||||||
def fetch_object_from_id(id, options \\ []) do
|
def fetch_object_from_id(id, options \\ []) do
|
||||||
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
|
||||||
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
{_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
|
||||||
|
@ -141,6 +145,7 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
Logger.debug("Fetching object #{id} via AP")
|
Logger.debug("Fetching object #{id} via AP")
|
||||||
|
|
||||||
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
|
||||||
|
{_, true} <- {:mrf, MRF.id_filter(id)},
|
||||||
{:ok, body} <- get_object(id),
|
{:ok, body} <- get_object(id),
|
||||||
{:ok, data} <- safe_json_decode(body),
|
{:ok, data} <- safe_json_decode(body),
|
||||||
:ok <- Containment.contain_origin_from_id(id, data) do
|
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||||
|
@ -156,6 +161,9 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
{:error, e} ->
|
{:error, e} ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
||||||
|
{:mrf, false} ->
|
||||||
|
{:error, {:reject, "Filtered by id"}}
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,17 +16,24 @@ defmodule Pleroma.ReleaseTasks do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_module(task) do
|
||||||
|
module_name =
|
||||||
|
task
|
||||||
|
|> String.split(".")
|
||||||
|
|> Enum.map(&String.capitalize/1)
|
||||||
|
|> then(fn x -> [Mix, Tasks, Pleroma] ++ x end)
|
||||||
|
|> Module.concat()
|
||||||
|
|
||||||
|
case Code.ensure_loaded(module_name) do
|
||||||
|
{:module, _} -> module_name
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp mix_task(task, args) do
|
defp mix_task(task, args) do
|
||||||
Application.load(:pleroma)
|
Application.load(:pleroma)
|
||||||
{:ok, modules} = :application.get_key(:pleroma, :modules)
|
|
||||||
|
|
||||||
module =
|
module = find_module(task)
|
||||||
Enum.find(modules, fn module ->
|
|
||||||
module = Module.split(module)
|
|
||||||
|
|
||||||
match?(["Mix", "Tasks", "Pleroma" | _], module) and
|
|
||||||
String.downcase(List.last(module)) == task
|
|
||||||
end)
|
|
||||||
|
|
||||||
if module do
|
if module do
|
||||||
module.run(args)
|
module.run(args)
|
||||||
|
|
|
@ -122,6 +122,7 @@ defmodule Pleroma.Search.Meilisearch do
|
||||||
# Only index public or unlisted Notes
|
# Only index public or unlisted Notes
|
||||||
if not is_nil(object) and object.data["type"] == "Note" and
|
if not is_nil(object) and object.data["type"] == "Note" and
|
||||||
not is_nil(object.data["content"]) and
|
not is_nil(object.data["content"]) and
|
||||||
|
not is_nil(object.data["published"]) and
|
||||||
(Pleroma.Constants.as_public() in object.data["to"] or
|
(Pleroma.Constants.as_public() in object.data["to"] or
|
||||||
Pleroma.Constants.as_public() in object.data["cc"]) and
|
Pleroma.Constants.as_public() in object.data["cc"]) and
|
||||||
object.data["content"] not in ["", "."] do
|
object.data["content"] not in ["", "."] do
|
||||||
|
|
|
@ -90,9 +90,13 @@ defmodule Pleroma.Upload.Filter.AnalyzeMetadata do
|
||||||
{:ok, rgb} =
|
{:ok, rgb} =
|
||||||
if Image.has_alpha?(resized_image) do
|
if Image.has_alpha?(resized_image) do
|
||||||
# remove alpha channel
|
# remove alpha channel
|
||||||
resized_image
|
case Operation.extract_band(resized_image, 0, n: 3) do
|
||||||
|> Operation.extract_band!(0, n: 3)
|
{:ok, data} ->
|
||||||
|> Image.write_to_binary()
|
Image.write_to_binary(data)
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Image.write_to_binary(resized_image)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
Image.write_to_binary(resized_image)
|
Image.write_to_binary(resized_image)
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,8 +17,16 @@ defmodule Pleroma.Upload.Filter.Dedupe do
|
||||||
|> Base.encode16(case: :lower)
|
|> Base.encode16(case: :lower)
|
||||||
|
|
||||||
filename = shasum <> "." <> extension
|
filename = shasum <> "." <> extension
|
||||||
{:ok, :filtered, %Upload{upload | id: shasum, path: filename}}
|
|
||||||
|
{:ok, :filtered, %Upload{upload | id: shasum, path: shard_path(filename)}}
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter(_), do: {:ok, :noop}
|
def filter(_), do: {:ok, :noop}
|
||||||
|
|
||||||
|
@spec shard_path(String.t()) :: String.t()
|
||||||
|
def shard_path(
|
||||||
|
<<a::binary-size(2), b::binary-size(2), c::binary-size(2), _::binary>> = filename
|
||||||
|
) do
|
||||||
|
Path.join([a, b, c, filename])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -419,6 +419,11 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def image_description(image, default \\ "")
|
||||||
|
|
||||||
|
def image_description(%{"name" => name}, _default), do: name
|
||||||
|
def image_description(_, default), do: default
|
||||||
|
|
||||||
# Should probably be renamed or removed
|
# Should probably be renamed or removed
|
||||||
@spec ap_id(User.t()) :: String.t()
|
@spec ap_id(User.t()) :: String.t()
|
||||||
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
|
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
|
||||||
|
@ -586,16 +591,26 @@ defmodule Pleroma.User do
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, min: 1, max: name_limit)
|
|> validate_length(:name, min: 1, max: name_limit)
|
||||||
|> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types())
|
|> validate_inclusion(:actor_type, Pleroma.Constants.allowed_user_actor_types())
|
||||||
|
|> validate_image_description(:avatar_description, params)
|
||||||
|
|> validate_image_description(:header_description, params)
|
||||||
|> put_fields()
|
|> put_fields()
|
||||||
|> put_emoji()
|
|> put_emoji()
|
||||||
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
|> put_change_if_present(:bio, &{:ok, parse_bio(&1, struct)})
|
||||||
|> put_change_if_present(:avatar, &put_upload(&1, :avatar))
|
|> put_change_if_present(
|
||||||
|> put_change_if_present(:banner, &put_upload(&1, :banner))
|
:avatar,
|
||||||
|
&put_upload(&1, :avatar, Map.get(params, :avatar_description))
|
||||||
|
)
|
||||||
|
|> put_change_if_present(
|
||||||
|
:banner,
|
||||||
|
&put_upload(&1, :banner, Map.get(params, :header_description))
|
||||||
|
)
|
||||||
|> put_change_if_present(:background, &put_upload(&1, :background))
|
|> put_change_if_present(:background, &put_upload(&1, :background))
|
||||||
|> put_change_if_present(
|
|> put_change_if_present(
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||||
)
|
)
|
||||||
|
|> maybe_update_image_description(:avatar, Map.get(params, :avatar_description))
|
||||||
|
|> maybe_update_image_description(:banner, Map.get(params, :header_description))
|
||||||
|> validate_fields(false)
|
|> validate_fields(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -674,13 +689,41 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_upload(value, type) do
|
defp put_upload(value, type, description \\ nil) do
|
||||||
with %Plug.Upload{} <- value,
|
with %Plug.Upload{} <- value,
|
||||||
{:ok, object} <- ActivityPub.upload(value, type: type) do
|
{:ok, object} <- ActivityPub.upload(value, type: type, description: description) do
|
||||||
{:ok, object.data}
|
{:ok, object.data}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_image_description(changeset, key, params) do
|
||||||
|
description_limit = Config.get([:instance, :description_limit], 5_000)
|
||||||
|
description = Map.get(params, key)
|
||||||
|
|
||||||
|
if is_binary(description) and String.length(description) > description_limit do
|
||||||
|
changeset
|
||||||
|
|> add_error(key, "#{key} is too long")
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_update_image_description(changeset, image_field, description)
|
||||||
|
when is_binary(description) do
|
||||||
|
with {:image_missing, true} <- {:image_missing, not changed?(changeset, image_field)},
|
||||||
|
{:existing_image, %{"id" => id}} <-
|
||||||
|
{:existing_image, Map.get(changeset.data, image_field)},
|
||||||
|
{:object, %Object{} = object} <- {:object, Object.get_by_ap_id(id)},
|
||||||
|
{:ok, object} <- Object.update_data(object, %{"name" => description}) do
|
||||||
|
put_change(changeset, image_field, object.data)
|
||||||
|
else
|
||||||
|
{:description_too_long, true} -> {:error}
|
||||||
|
_ -> changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_update_image_description(changeset, _, _), do: changeset
|
||||||
|
|
||||||
def update_as_admin_changeset(struct, params) do
|
def update_as_admin_changeset(struct, params) do
|
||||||
struct
|
struct
|
||||||
|> update_changeset(params)
|
|> update_changeset(params)
|
||||||
|
|
|
@ -92,9 +92,6 @@ defmodule Pleroma.User.Backup do
|
||||||
else
|
else
|
||||||
true ->
|
true ->
|
||||||
{:error, "Backup is missing id. Please insert it into the Repo first."}
|
{:error, "Backup is missing id. Please insert it into the Repo first."}
|
||||||
|
|
||||||
e ->
|
|
||||||
{:error, e}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -121,14 +118,13 @@ defmodule Pleroma.User.Backup do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp permitted?(user) do
|
defp permitted?(user) do
|
||||||
with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)},
|
with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)} do
|
||||||
days = Config.get([__MODULE__, :limit_days]),
|
days = Config.get([__MODULE__, :limit_days])
|
||||||
diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days),
|
diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days)
|
||||||
{_, true} <- {:diff, diff > days} do
|
|
||||||
true
|
diff > days
|
||||||
else
|
else
|
||||||
{:last, nil} -> true
|
{:last, nil} -> true
|
||||||
{:diff, false} -> false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -250,7 +246,13 @@ defmodule Pleroma.User.Backup do
|
||||||
defp actor(dir, user) do
|
defp actor(dir, user) do
|
||||||
with {:ok, json} <-
|
with {:ok, json} <-
|
||||||
UserView.render("user.json", %{user: user})
|
UserView.render("user.json", %{user: user})
|
||||||
|> Map.merge(%{"likes" => "likes.json", "bookmarks" => "bookmarks.json"})
|
|> Map.merge(%{
|
||||||
|
"bookmarks" => "bookmarks.json",
|
||||||
|
"likes" => "likes.json",
|
||||||
|
"outbox" => "outbox.json",
|
||||||
|
"followers" => "followers.json",
|
||||||
|
"following" => "following.json"
|
||||||
|
})
|
||||||
|> Jason.encode() do
|
|> Jason.encode() do
|
||||||
File.write(Path.join(dir, "actor.json"), json)
|
File.write(Path.join(dir, "actor.json"), json)
|
||||||
end
|
end
|
||||||
|
@ -297,9 +299,6 @@ defmodule Pleroma.User.Backup do
|
||||||
)
|
)
|
||||||
|
|
||||||
acc
|
acc
|
||||||
|
|
||||||
_ ->
|
|
||||||
acc
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -5,87 +5,107 @@
|
||||||
defmodule Pleroma.User.Import do
|
defmodule Pleroma.User.Import do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()}
|
@spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()}
|
||||||
def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do
|
def perform(:mute_import, %User{} = user, actor) do
|
||||||
Enum.map(
|
with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor),
|
||||||
identifiers,
|
{_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)},
|
||||||
fn identifier ->
|
|
||||||
with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier),
|
|
||||||
{:ok, _} <- User.mute(user, muted_user) do
|
{:ok, _} <- User.mute(user, muted_user) do
|
||||||
muted_user
|
{:ok, muted_user}
|
||||||
else
|
else
|
||||||
error -> handle_error(:mutes_import, identifier, error)
|
{:existing_mute, true} -> :ok
|
||||||
|
error -> handle_error(:mutes_import, actor, error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do
|
def perform(:block_import, %User{} = user, actor) do
|
||||||
Enum.map(
|
with {:ok, %User{} = blocked} <- User.get_or_fetch(actor),
|
||||||
identifiers,
|
{_, false} <- {:existing_block, User.blocks_user?(user, blocked)},
|
||||||
fn identifier ->
|
{:ok, _block} <- CommonAPI.block(blocked, user) do
|
||||||
with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier),
|
{:ok, blocked}
|
||||||
{:ok, _block} <- CommonAPI.block(blocked, blocker) do
|
|
||||||
blocked
|
|
||||||
else
|
else
|
||||||
error -> handle_error(:blocks_import, identifier, error)
|
{:existing_block, true} -> :ok
|
||||||
|
error -> handle_error(:blocks_import, actor, error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do
|
def perform(:follow_import, %User{} = user, actor) do
|
||||||
Enum.map(
|
with {:ok, %User{} = followed} <- User.get_or_fetch(actor),
|
||||||
identifiers,
|
{_, false} <- {:existing_follow, User.following?(user, followed)},
|
||||||
fn identifier ->
|
{:ok, user, followed} <- User.maybe_direct_follow(user, followed),
|
||||||
with {:ok, %User{} = followed} <- User.get_or_fetch(identifier),
|
{:ok, _, _, _} <- CommonAPI.follow(followed, user) do
|
||||||
{:ok, follower, followed} <- User.maybe_direct_follow(follower, followed),
|
{:ok, followed}
|
||||||
{:ok, _, _, _} <- CommonAPI.follow(followed, follower) do
|
|
||||||
followed
|
|
||||||
else
|
else
|
||||||
error -> handle_error(:follow_import, identifier, error)
|
{:existing_follow, true} -> :ok
|
||||||
|
error -> handle_error(:follow_import, actor, error)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform(_, _, _), do: :ok
|
|
||||||
|
|
||||||
defp handle_error(op, user_id, error) do
|
defp handle_error(op, user_id, error) do
|
||||||
Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}")
|
Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}")
|
||||||
error
|
{:error, error}
|
||||||
end
|
end
|
||||||
|
|
||||||
def blocks_import(%User{} = blocker, [_ | _] = identifiers) do
|
def blocks_import(%User{} = user, [_ | _] = actors) do
|
||||||
|
jobs =
|
||||||
|
Repo.checkout(fn ->
|
||||||
|
Enum.reduce(actors, [], fn actor, acc ->
|
||||||
|
{:ok, job} =
|
||||||
BackgroundWorker.new(%{
|
BackgroundWorker.new(%{
|
||||||
"op" => "blocks_import",
|
"op" => "block_import",
|
||||||
"user_id" => blocker.id,
|
"user_id" => user.id,
|
||||||
"identifiers" => identifiers
|
"actor" => actor
|
||||||
})
|
})
|
||||||
|> Oban.insert()
|
|> Oban.insert()
|
||||||
|
|
||||||
|
acc ++ [job]
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, jobs}
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_import(%User{} = follower, [_ | _] = identifiers) do
|
def follows_import(%User{} = user, [_ | _] = actors) do
|
||||||
|
jobs =
|
||||||
|
Repo.checkout(fn ->
|
||||||
|
Enum.reduce(actors, [], fn actor, acc ->
|
||||||
|
{:ok, job} =
|
||||||
BackgroundWorker.new(%{
|
BackgroundWorker.new(%{
|
||||||
"op" => "follow_import",
|
"op" => "follow_import",
|
||||||
"user_id" => follower.id,
|
"user_id" => user.id,
|
||||||
"identifiers" => identifiers
|
"actor" => actor
|
||||||
})
|
})
|
||||||
|> Oban.insert()
|
|> Oban.insert()
|
||||||
|
|
||||||
|
acc ++ [job]
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, jobs}
|
||||||
end
|
end
|
||||||
|
|
||||||
def mutes_import(%User{} = user, [_ | _] = identifiers) do
|
def mutes_import(%User{} = user, [_ | _] = actors) do
|
||||||
|
jobs =
|
||||||
|
Repo.checkout(fn ->
|
||||||
|
Enum.reduce(actors, [], fn actor, acc ->
|
||||||
|
{:ok, job} =
|
||||||
BackgroundWorker.new(%{
|
BackgroundWorker.new(%{
|
||||||
"op" => "mutes_import",
|
"op" => "mute_import",
|
||||||
"user_id" => user.id,
|
"user_id" => user.id,
|
||||||
"identifiers" => identifiers
|
"actor" => actor
|
||||||
})
|
})
|
||||||
|> Oban.insert()
|
|> Oban.insert()
|
||||||
|
|
||||||
|
acc ++ [job]
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, jobs}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1542,16 +1542,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
|
|
||||||
defp get_actor_url(_url), do: nil
|
defp get_actor_url(_url), do: nil
|
||||||
|
|
||||||
defp normalize_image(%{"url" => url}) do
|
defp normalize_image(%{"url" => url} = data) do
|
||||||
%{
|
%{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
"url" => [%{"href" => url}]
|
"url" => [%{"href" => url}]
|
||||||
}
|
}
|
||||||
|
|> maybe_put_description(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
|
||||||
defp normalize_image(_), do: nil
|
defp normalize_image(_), do: nil
|
||||||
|
|
||||||
|
defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do
|
||||||
|
Map.put(map, "name", description)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_description(map, _), do: map
|
||||||
|
|
||||||
defp object_to_user_data(data, additional) do
|
defp object_to_user_data(data, additional) do
|
||||||
fields =
|
fields =
|
||||||
data
|
data
|
||||||
|
|
|
@ -311,7 +311,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
post_inbox_relayed_create(conn, params)
|
post_inbox_relayed_create(conn, params)
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> put_status(:bad_request)
|
|> put_status(403)
|
||||||
|> json("Not federating")
|
|> json("Not federating")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -482,7 +482,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
|> put_status(:forbidden)
|
|> put_status(:forbidden)
|
||||||
|> json(message)
|
|> json(message)
|
||||||
|
|
||||||
{:error, message} ->
|
{:error, message} when is_binary(message) ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:bad_request)
|
|> put_status(:bad_request)
|
||||||
|> json(message)
|
|> json(message)
|
||||||
|
|
|
@ -108,6 +108,14 @@ defmodule Pleroma.Web.ActivityPub.MRF do
|
||||||
|
|
||||||
def filter(%{} = object), do: get_policies() |> filter(object)
|
def filter(%{} = object), do: get_policies() |> filter(object)
|
||||||
|
|
||||||
|
def id_filter(policies, id) when is_binary(id) do
|
||||||
|
policies
|
||||||
|
|> Enum.filter(&function_exported?(&1, :id_filter, 1))
|
||||||
|
|> Enum.all?(& &1.id_filter(id))
|
||||||
|
end
|
||||||
|
|
||||||
|
def id_filter(id) when is_binary(id), do: get_policies() |> id_filter(id)
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def pipeline_filter(%{} = message, meta) do
|
def pipeline_filter(%{} = message, meta) do
|
||||||
object = meta[:object_data]
|
object = meta[:object_data]
|
||||||
|
|
|
@ -13,6 +13,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
|
||||||
{:reject, activity}
|
{:reject, activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def id_filter(id) do
|
||||||
|
Logger.debug("REJECTING #{id}")
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe, do: {:ok, %{}}
|
def describe, do: {:ok, %{}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
|
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
|
||||||
@callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
|
@callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
|
||||||
|
@callback id_filter(String.t()) :: boolean()
|
||||||
@callback describe() :: {:ok | :error, map()}
|
@callback describe() :: {:ok | :error, map()}
|
||||||
@callback config_description() :: %{
|
@callback config_description() :: %{
|
||||||
optional(:children) => [map()],
|
optional(:children) => [map()],
|
||||||
|
@ -13,5 +14,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.Policy do
|
||||||
description: String.t()
|
description: String.t()
|
||||||
}
|
}
|
||||||
@callback history_awareness() :: :auto | :manual
|
@callback history_awareness() :: :auto | :manual
|
||||||
@optional_callbacks config_description: 0, history_awareness: 0
|
@optional_callbacks config_description: 0, history_awareness: 0, id_filter: 1
|
||||||
end
|
end
|
||||||
|
|
118
lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex
Normal file
118
lib/pleroma/web/activity_pub/mrf/remote_report_policy.ex
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy do
|
||||||
|
@moduledoc "Drop remote reports if they don't contain enough information."
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF.Policy
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(%{"type" => "Flag"} = object) do
|
||||||
|
with {_, false} <- {:local, local?(object)},
|
||||||
|
{:ok, _} <- maybe_reject_all(object),
|
||||||
|
{:ok, _} <- maybe_reject_anonymous(object),
|
||||||
|
{:ok, _} <- maybe_reject_third_party(object),
|
||||||
|
{:ok, _} <- maybe_reject_empty_message(object) do
|
||||||
|
{:ok, object}
|
||||||
|
else
|
||||||
|
{:local, true} -> {:ok, object}
|
||||||
|
{:reject, message} -> {:reject, message}
|
||||||
|
error -> {:reject, error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
defp maybe_reject_all(object) do
|
||||||
|
if Config.get([:mrf_remote_report, :reject_all]) do
|
||||||
|
{:reject, "[RemoteReportPolicy] Remote report"}
|
||||||
|
else
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reject_anonymous(%{"actor" => actor} = object) do
|
||||||
|
with true <- Config.get([:mrf_remote_report, :reject_anonymous]),
|
||||||
|
%URI{path: "/actor"} <- URI.parse(actor) do
|
||||||
|
{:reject, "[RemoteReportPolicy] Anonymous: #{actor}"}
|
||||||
|
else
|
||||||
|
_ -> {:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reject_third_party(%{"object" => objects} = object) do
|
||||||
|
{_, to} =
|
||||||
|
case objects do
|
||||||
|
[head | tail] when is_binary(head) -> {tail, head}
|
||||||
|
s when is_binary(s) -> {[], s}
|
||||||
|
_ -> {[], ""}
|
||||||
|
end
|
||||||
|
|
||||||
|
with true <- Config.get([:mrf_remote_report, :reject_third_party]),
|
||||||
|
false <- String.starts_with?(to, Pleroma.Web.Endpoint.url()) do
|
||||||
|
{:reject, "[RemoteReportPolicy] Third-party: #{to}"}
|
||||||
|
else
|
||||||
|
_ -> {:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reject_empty_message(%{"content" => content} = object)
|
||||||
|
when is_binary(content) and content != "" do
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_reject_empty_message(object) do
|
||||||
|
if Config.get([:mrf_remote_report, :reject_empty_message]) do
|
||||||
|
{:reject, ["RemoteReportPolicy] No content"]}
|
||||||
|
else
|
||||||
|
{:ok, object}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp local?(%{"actor" => actor}) do
|
||||||
|
String.starts_with?(actor, Pleroma.Web.Endpoint.url())
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
mrf_remote_report =
|
||||||
|
Config.get(:mrf_remote_report)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
{:ok, %{mrf_remote_report: mrf_remote_report}}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_remote_report,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.RemoteReportPolicy",
|
||||||
|
label: "MRF Remote Report",
|
||||||
|
description: "Drop remote reports if they don't contain enough information.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :reject_all,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Reject all remote reports? (this option takes precedence)",
|
||||||
|
suggestions: [false]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject_anonymous,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Reject anonymous remote reports?",
|
||||||
|
suggestions: [true]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject_third_party,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Reject reports on users from third-party instances?",
|
||||||
|
suggestions: [true]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject_empty_message,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Reject remote reports with no message?",
|
||||||
|
suggestions: [true]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -191,6 +191,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||||
|> MRF.instance_list_from_tuples()
|
|> MRF.instance_list_from_tuples()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def id_filter(id) do
|
||||||
|
host_info = URI.parse(id)
|
||||||
|
|
||||||
|
with {:ok, _} <- check_accept(host_info, %{}),
|
||||||
|
{:ok, _} <- check_reject(host_info, %{}) do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"type" => "Delete", "actor" => actor} = activity) do
|
def filter(%{"type" => "Delete", "actor" => actor} = activity) do
|
||||||
%{host: actor_host} = URI.parse(actor)
|
%{host: actor_host} = URI.parse(actor)
|
||||||
|
|
|
@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
||||||
|
|
||||||
|
import Pleroma.Constants, only: [activity_types: 0, object_types: 0]
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -39,6 +41,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
@impl true
|
@impl true
|
||||||
def validate(object, meta)
|
def validate(object, meta)
|
||||||
|
|
||||||
|
# This overload works together with the InboxGuardPlug
|
||||||
|
# and ensures that we are not accepting any activity type
|
||||||
|
# that cannot pass InboxGuardPlug.
|
||||||
|
# If we want to support any more activity types, make sure to
|
||||||
|
# add it in Pleroma.Constants's activity_types or object_types,
|
||||||
|
# and, if applicable, allowed_activity_types_from_strangers.
|
||||||
|
def validate(%{"type" => type}, _meta)
|
||||||
|
when type not in activity_types() and type not in object_types(),
|
||||||
|
do: {:error, :not_allowed_object_type}
|
||||||
|
|
||||||
def validate(%{"type" => "Block"} = block_activity, meta) do
|
def validate(%{"type" => "Block"} = block_activity, meta) do
|
||||||
with {:ok, block_activity} <-
|
with {:ok, block_activity} <-
|
||||||
block_activity
|
block_activity
|
||||||
|
@ -165,7 +177,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
meta = Keyword.put(meta, :object_data, object_data),
|
meta = Keyword.put(meta, :object_data, object_data),
|
||||||
{:ok, update_activity} <-
|
{:ok, update_activity} <-
|
||||||
update_activity
|
update_activity
|
||||||
|> UpdateValidator.cast_and_validate()
|
|> UpdateValidator.cast_and_validate(meta)
|
||||||
|> Ecto.Changeset.apply_action(:insert) do
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
update_activity = stringify_keys(update_activity)
|
update_activity = stringify_keys(update_activity)
|
||||||
{:ok, update_activity, meta}
|
{:ok, update_activity, meta}
|
||||||
|
@ -173,7 +185,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
{:local, _} ->
|
{:local, _} ->
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
update_activity
|
update_activity
|
||||||
|> UpdateValidator.cast_and_validate()
|
|> UpdateValidator.cast_and_validate(meta)
|
||||||
|> Ecto.Changeset.apply_action(:insert) do
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
object = stringify_keys(object)
|
object = stringify_keys(object)
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
@ -203,9 +215,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
"Answer" -> AnswerValidator
|
"Answer" -> AnswerValidator
|
||||||
end
|
end
|
||||||
|
|
||||||
|
cast_func =
|
||||||
|
if type == "Update" do
|
||||||
|
fn o -> validator.cast_and_validate(o, meta) end
|
||||||
|
else
|
||||||
|
fn o -> validator.cast_and_validate(o) end
|
||||||
|
end
|
||||||
|
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
object
|
object
|
||||||
|> validator.cast_and_validate()
|
|> cast_func.()
|
||||||
|> Ecto.Changeset.apply_action(:insert) do
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
object = stringify_keys(object)
|
object = stringify_keys(object)
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
|
|
@ -85,6 +85,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
|
||||||
|> fix_replies()
|
|> fix_replies()
|
||||||
|> fix_attachments()
|
|> fix_attachments()
|
||||||
|> CommonFixes.fix_quote_url()
|
|> CommonFixes.fix_quote_url()
|
||||||
|
|> CommonFixes.fix_likes()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> Transmogrifier.fix_content_map()
|
|> Transmogrifier.fix_content_map()
|
||||||
|> CommonFixes.maybe_add_language()
|
|> CommonFixes.maybe_add_language()
|
||||||
|
|
|
@ -100,6 +100,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator do
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_object_defaults()
|
|> CommonFixes.fix_object_defaults()
|
||||||
|> CommonFixes.fix_quote_url()
|
|> CommonFixes.fix_quote_url()
|
||||||
|
|> CommonFixes.fix_likes()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> fix_url()
|
|> fix_url()
|
||||||
|> fix_content()
|
|> fix_content()
|
||||||
|
|
|
@ -119,6 +119,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
|
|
||||||
def fix_quote_url(data), do: data
|
def fix_quote_url(data), do: data
|
||||||
|
|
||||||
|
# On Mastodon, `"likes"` attribute includes an inlined `Collection` with `totalItems`,
|
||||||
|
# not a list of users.
|
||||||
|
# https://github.com/mastodon/mastodon/pull/32007
|
||||||
|
def fix_likes(%{"likes" => %{}} = data), do: Map.drop(data, ["likes"])
|
||||||
|
|
||||||
|
def fix_likes(data), do: data
|
||||||
|
|
||||||
# https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
|
# https://codeberg.org/fediverse/fep/src/branch/main/fep/e232/fep-e232.md
|
||||||
def object_link_tag?(%{
|
def object_link_tag?(%{
|
||||||
"type" => "Link",
|
"type" => "Link",
|
||||||
|
|
|
@ -48,6 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EventValidator do
|
||||||
data
|
data
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_object_defaults()
|
|> CommonFixes.fix_object_defaults()
|
||||||
|
|> CommonFixes.fix_likes()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> CommonFixes.maybe_add_language()
|
|> CommonFixes.maybe_add_language()
|
||||||
|> CommonFixes.maybe_add_content_map()
|
|> CommonFixes.maybe_add_content_map()
|
||||||
|
|
|
@ -64,6 +64,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
|
||||||
|> CommonFixes.fix_actor()
|
|> CommonFixes.fix_actor()
|
||||||
|> CommonFixes.fix_object_defaults()
|
|> CommonFixes.fix_object_defaults()
|
||||||
|> CommonFixes.fix_quote_url()
|
|> CommonFixes.fix_quote_url()
|
||||||
|
|> CommonFixes.fix_likes()
|
||||||
|> Transmogrifier.fix_emoji()
|
|> Transmogrifier.fix_emoji()
|
||||||
|> fix_closed()
|
|> fix_closed()
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -31,23 +33,50 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_data(cng) do
|
defp validate_data(cng, meta) do
|
||||||
cng
|
cng
|
||||||
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|> validate_inclusion(:type, ["Update"])
|
|> validate_inclusion(:type, ["Update"])
|
||||||
|> validate_actor_presence()
|
|> validate_actor_presence()
|
||||||
|> validate_updating_rights()
|
|> validate_updating_rights(meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data, meta \\ []) do
|
||||||
data
|
data
|
||||||
|> cast_data
|
|> cast_data
|
||||||
|> validate_data
|
|> validate_data(meta)
|
||||||
end
|
end
|
||||||
|
|
||||||
# For now we only support updating users, and here the rule is easy:
|
def validate_updating_rights(cng, meta) do
|
||||||
# object id == actor id
|
if meta[:local] do
|
||||||
def validate_updating_rights(cng) do
|
validate_updating_rights_local(cng)
|
||||||
|
else
|
||||||
|
validate_updating_rights_remote(cng)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# For local Updates, verify the actor can edit the object
|
||||||
|
def validate_updating_rights_local(cng) do
|
||||||
|
actor = get_field(cng, :actor)
|
||||||
|
updated_object = get_field(cng, :object)
|
||||||
|
|
||||||
|
if {:ok, actor} == ObjectValidators.ObjectID.cast(updated_object) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
with %User{} = user <- User.get_cached_by_ap_id(actor),
|
||||||
|
{_, %Object{} = orig_object} <- {:object, Object.normalize(updated_object)},
|
||||||
|
:ok <- Object.authorize_access(orig_object, user) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
cng
|
||||||
|
|> add_error(:object, "Can't be updated by this actor")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# For remote Updates, verify the host is the same.
|
||||||
|
def validate_updating_rights_remote(cng) do
|
||||||
with actor = get_field(cng, :actor),
|
with actor = get_field(cng, :actor),
|
||||||
object = get_field(cng, :object),
|
object = get_field(cng, :object),
|
||||||
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
|
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
|
||||||
|
|
|
@ -22,22 +22,27 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||||
defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
|
defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub)
|
||||||
defp config, do: Config.get([:pipeline, :config], Config)
|
defp config, do: Config.get([:pipeline, :config], Config)
|
||||||
|
|
||||||
@spec common_pipeline(map(), keyword()) ::
|
@type results :: {:ok, Activity.t() | Object.t(), keyword()}
|
||||||
{:ok, Activity.t() | Object.t(), keyword()} | {:error | :reject, any()}
|
@type errors :: {:error | :reject, any()}
|
||||||
|
|
||||||
|
# The Repo.transaction will wrap the result in an {:ok, _}
|
||||||
|
# and only returns an {:error, _} if the error encountered was related
|
||||||
|
# to the SQL transaction
|
||||||
|
@spec common_pipeline(map(), keyword()) :: results() | errors()
|
||||||
def common_pipeline(object, meta) do
|
def common_pipeline(object, meta) do
|
||||||
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do
|
||||||
{:ok, {:ok, activity, meta}} ->
|
{:ok, {:ok, activity, meta}} ->
|
||||||
side_effects().handle_after_transaction(meta)
|
side_effects().handle_after_transaction(meta)
|
||||||
{:ok, activity, meta}
|
{:ok, activity, meta}
|
||||||
|
|
||||||
{:ok, value} ->
|
{:ok, {:error, _} = error} ->
|
||||||
value
|
error
|
||||||
|
|
||||||
|
{:ok, {:reject, _} = error} ->
|
||||||
|
error
|
||||||
|
|
||||||
{:error, e} ->
|
{:error, e} ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
||||||
{:reject, e} ->
|
|
||||||
{:reject, e}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -127,10 +127,25 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
"capabilities" => capabilities,
|
"capabilities" => capabilities,
|
||||||
"alsoKnownAs" => user.also_known_as,
|
"alsoKnownAs" => user.also_known_as,
|
||||||
"vcard:bday" => birthday,
|
"vcard:bday" => birthday,
|
||||||
"webfinger" => "acct:#{User.full_nickname(user)}"
|
"webfinger" => "acct:#{User.full_nickname(user)}",
|
||||||
|
"published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at)
|
||||||
}
|
}
|
||||||
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|
|> Map.merge(
|
||||||
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|
maybe_make_image(
|
||||||
|
&User.avatar_url/2,
|
||||||
|
User.image_description(user.avatar, nil),
|
||||||
|
"icon",
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Map.merge(
|
||||||
|
maybe_make_image(
|
||||||
|
&User.banner_url/2,
|
||||||
|
User.image_description(user.banner, nil),
|
||||||
|
"image",
|
||||||
|
user
|
||||||
|
)
|
||||||
|
)
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -305,16 +320,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_make_image(func, key, user) do
|
defp maybe_make_image(func, description, key, user) do
|
||||||
if image = func.(user, no_default: true) do
|
if image = func.(user, no_default: true) do
|
||||||
%{
|
%{
|
||||||
key => %{
|
key =>
|
||||||
|
%{
|
||||||
"type" => "Image",
|
"type" => "Image",
|
||||||
"url" => image
|
"url" => image
|
||||||
}
|
}
|
||||||
|
|> maybe_put_description(description)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
%{}
|
%{}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_put_description(map, description) when is_binary(description) do
|
||||||
|
Map.put(map, "name", description)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_description(map, _description), do: map
|
||||||
end
|
end
|
||||||
|
|
|
@ -813,6 +813,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||||
allOf: [BooleanLike],
|
allOf: [BooleanLike],
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "User's birthday will be visible"
|
description: "User's birthday will be visible"
|
||||||
|
},
|
||||||
|
avatar_description: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: "Avatar image description."
|
||||||
|
},
|
||||||
|
header_description: %Schema{
|
||||||
|
type: :string,
|
||||||
|
nullable: true,
|
||||||
|
description: "Header image description."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
|
|
|
@ -121,7 +121,7 @@ defmodule Pleroma.Web.ApiSpec.MediaOperation do
|
||||||
security: [%{"oAuth" => ["write:media"]}],
|
security: [%{"oAuth" => ["write:media"]}],
|
||||||
requestBody: Helpers.request_body("Parameters", create_request()),
|
requestBody: Helpers.request_body("Parameters", create_request()),
|
||||||
responses: %{
|
responses: %{
|
||||||
202 => Operation.response("Media", "application/json", Attachment),
|
200 => Operation.response("Media", "application/json", Attachment),
|
||||||
400 => Operation.response("Media", "application/json", ApiError),
|
400 => Operation.response("Media", "application/json", ApiError),
|
||||||
422 => Operation.response("Media", "application/json", ApiError),
|
422 => Operation.response("Media", "application/json", ApiError),
|
||||||
500 => Operation.response("Media", "application/json", ApiError)
|
500 => Operation.response("Media", "application/json", ApiError)
|
||||||
|
|
|
@ -158,6 +158,10 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
id: %Schema{type: :string},
|
id: %Schema{type: :string},
|
||||||
|
group_key: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "Group key shared by similar notifications"
|
||||||
|
},
|
||||||
type: notification_type(),
|
type: notification_type(),
|
||||||
created_at: %Schema{type: :string, format: :"date-time"},
|
created_at: %Schema{type: :string, format: :"date-time"},
|
||||||
account: %Schema{
|
account: %Schema{
|
||||||
|
@ -180,6 +184,7 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
"id" => "34975861",
|
"id" => "34975861",
|
||||||
|
"group-key" => "ungrouped-34975861",
|
||||||
"type" => "mention",
|
"type" => "mention",
|
||||||
"created_at" => "2019-11-23T07:49:02.064Z",
|
"created_at" => "2019-11-23T07:49:02.064Z",
|
||||||
"account" => Account.schema().example,
|
"account" => Account.schema().example,
|
||||||
|
|
|
@ -111,7 +111,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
format: :uri,
|
format: :uri,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Favicon image of the user's instance"
|
description: "Favicon image of the user's instance"
|
||||||
}
|
},
|
||||||
|
avatar_description: %Schema{type: :string},
|
||||||
|
header_description: %Schema{type: :string}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
source: %Schema{
|
source: %Schema{
|
||||||
|
@ -152,6 +154,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
example: %{
|
example: %{
|
||||||
"acct" => "foobar",
|
"acct" => "foobar",
|
||||||
"avatar" => "https://mypleroma.com/images/avi.png",
|
"avatar" => "https://mypleroma.com/images/avi.png",
|
||||||
|
"avatar_description" => "",
|
||||||
"avatar_static" => "https://mypleroma.com/images/avi.png",
|
"avatar_static" => "https://mypleroma.com/images/avi.png",
|
||||||
"bot" => false,
|
"bot" => false,
|
||||||
"created_at" => "2020-03-24T13:05:58.000Z",
|
"created_at" => "2020-03-24T13:05:58.000Z",
|
||||||
|
@ -162,6 +165,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
"followers_count" => 0,
|
"followers_count" => 0,
|
||||||
"following_count" => 1,
|
"following_count" => 1,
|
||||||
"header" => "https://mypleroma.com/images/banner.png",
|
"header" => "https://mypleroma.com/images/banner.png",
|
||||||
|
"header_description" => "",
|
||||||
"header_static" => "https://mypleroma.com/images/banner.png",
|
"header_static" => "https://mypleroma.com/images/banner.png",
|
||||||
"id" => "9tKi3esbG7OQgZ2920",
|
"id" => "9tKi3esbG7OQgZ2920",
|
||||||
"locked" => false,
|
"locked" => false,
|
||||||
|
|
|
@ -249,6 +249,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description:
|
description:
|
||||||
"A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned"
|
"A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned"
|
||||||
|
},
|
||||||
|
list_id: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
nullable: true,
|
||||||
|
description:
|
||||||
|
"The ID of the list the post is addressed to (if any, only returned to author)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,4 +10,9 @@ defmodule Pleroma.Web.Auth.Authenticator do
|
||||||
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
||||||
@callback auth_template() :: String.t() | nil
|
@callback auth_template() :: String.t() | nil
|
||||||
@callback oauth_consumer_template() :: String.t() | nil
|
@callback oauth_consumer_template() :: String.t() | nil
|
||||||
|
|
||||||
|
@callback change_password(Pleroma.User.t(), String.t(), String.t(), String.t()) ::
|
||||||
|
{:ok, Pleroma.User.t()} | {:error, term()}
|
||||||
|
|
||||||
|
@optional_callbacks change_password: 4
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,18 +3,14 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||||
|
alias Pleroma.LDAP
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
require Logger
|
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1]
|
||||||
|
|
||||||
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
|
|
||||||
|
|
||||||
@behaviour Pleroma.Web.Auth.Authenticator
|
@behaviour Pleroma.Web.Auth.Authenticator
|
||||||
@base Pleroma.Web.Auth.PleromaAuthenticator
|
@base Pleroma.Web.Auth.PleromaAuthenticator
|
||||||
|
|
||||||
@connection_timeout 10_000
|
|
||||||
@search_timeout 10_000
|
|
||||||
|
|
||||||
defdelegate get_registration(conn), to: @base
|
defdelegate get_registration(conn), to: @base
|
||||||
defdelegate create_from_registration(conn, registration), to: @base
|
defdelegate create_from_registration(conn, registration), to: @base
|
||||||
defdelegate handle_error(conn, error), to: @base
|
defdelegate handle_error(conn, error), to: @base
|
||||||
|
@ -24,7 +20,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||||
def get_user(%Plug.Conn{} = conn) do
|
def get_user(%Plug.Conn{} = conn) do
|
||||||
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
|
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
|
||||||
{:ok, {name, password}} <- fetch_credentials(conn),
|
{:ok, {name, password}} <- fetch_credentials(conn),
|
||||||
%User{} = user <- ldap_user(name, password) do
|
%User{} = user <- LDAP.bind_user(name, password) do
|
||||||
{:ok, user}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
{:ldap, _} ->
|
{:ldap, _} ->
|
||||||
|
@ -35,106 +31,12 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ldap_user(name, password) do
|
def change_password(user, password, new_password, new_password) do
|
||||||
ldap = Pleroma.Config.get(:ldap, [])
|
case LDAP.change_password(user.nickname, password, new_password) do
|
||||||
host = Keyword.get(ldap, :host, "localhost")
|
:ok -> {:ok, user}
|
||||||
port = Keyword.get(ldap, :port, 389)
|
e -> e
|
||||||
ssl = Keyword.get(ldap, :ssl, false)
|
|
||||||
sslopts = Keyword.get(ldap, :sslopts, [])
|
|
||||||
|
|
||||||
options =
|
|
||||||
[{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++
|
|
||||||
if sslopts != [], do: [{:sslopts, sslopts}], else: []
|
|
||||||
|
|
||||||
case :eldap.open([to_charlist(host)], options) do
|
|
||||||
{:ok, connection} ->
|
|
||||||
try do
|
|
||||||
if Keyword.get(ldap, :tls, false) do
|
|
||||||
:application.ensure_all_started(:ssl)
|
|
||||||
|
|
||||||
case :eldap.start_tls(
|
|
||||||
connection,
|
|
||||||
Keyword.get(ldap, :tlsopts, []),
|
|
||||||
@connection_timeout
|
|
||||||
) do
|
|
||||||
:ok ->
|
|
||||||
:ok
|
|
||||||
|
|
||||||
error ->
|
|
||||||
Logger.error("Could not start TLS: #{inspect(error)}")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
bind_user(connection, ldap, name, password)
|
def change_password(_, _, _, _), do: {:error, :password_confirmation}
|
||||||
after
|
|
||||||
:eldap.close(connection)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:error, error} ->
|
|
||||||
Logger.error("Could not open LDAP connection: #{inspect(error)}")
|
|
||||||
{:error, {:ldap_connection_error, error}}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp bind_user(connection, ldap, name, password) do
|
|
||||||
uid = Keyword.get(ldap, :uid, "cn")
|
|
||||||
base = Keyword.get(ldap, :base)
|
|
||||||
|
|
||||||
case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do
|
|
||||||
:ok ->
|
|
||||||
case fetch_user(name) do
|
|
||||||
%User{} = user ->
|
|
||||||
user
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
register_user(connection, base, uid, name)
|
|
||||||
end
|
|
||||||
|
|
||||||
error ->
|
|
||||||
Logger.error("Could not bind LDAP user #{name}: #{inspect(error)}")
|
|
||||||
{:error, {:ldap_bind_error, error}}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp register_user(connection, base, uid, name) do
|
|
||||||
case :eldap.search(connection, [
|
|
||||||
{:base, to_charlist(base)},
|
|
||||||
{:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))},
|
|
||||||
{:scope, :eldap.wholeSubtree()},
|
|
||||||
{:timeout, @search_timeout}
|
|
||||||
]) do
|
|
||||||
# The :eldap_search_result record structure changed in OTP 24.3 and added a controls field
|
|
||||||
# https://github.com/erlang/otp/pull/5538
|
|
||||||
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals}} ->
|
|
||||||
try_register(name, attributes)
|
|
||||||
|
|
||||||
{:ok, {:eldap_search_result, [{:eldap_entry, _object, attributes}], _referrals, _controls}} ->
|
|
||||||
try_register(name, attributes)
|
|
||||||
|
|
||||||
error ->
|
|
||||||
Logger.error("Couldn't register user because LDAP search failed: #{inspect(error)}")
|
|
||||||
{:error, {:ldap_search_error, error}}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp try_register(name, attributes) do
|
|
||||||
params = %{
|
|
||||||
name: name,
|
|
||||||
nickname: name,
|
|
||||||
password: nil
|
|
||||||
}
|
|
||||||
|
|
||||||
params =
|
|
||||||
case List.keyfind(attributes, ~c"mail", 0) do
|
|
||||||
{_, [mail]} -> Map.put_new(params, :email, :erlang.list_to_binary(mail))
|
|
||||||
_ -> params
|
|
||||||
end
|
|
||||||
|
|
||||||
changeset = User.register_changeset_ldap(%User{}, params)
|
|
||||||
|
|
||||||
case User.register(changeset) do
|
|
||||||
{:ok, user} -> user
|
|
||||||
error -> error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||||
alias Pleroma.Registration
|
alias Pleroma.Registration
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.Plugs.AuthenticationPlug
|
alias Pleroma.Web.Plugs.AuthenticationPlug
|
||||||
|
|
||||||
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
|
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
|
||||||
|
@ -101,4 +102,23 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
|
||||||
def auth_template, do: nil
|
def auth_template, do: nil
|
||||||
|
|
||||||
def oauth_consumer_template, do: nil
|
def oauth_consumer_template, do: nil
|
||||||
|
|
||||||
|
@doc "Changes Pleroma.User password in the database"
|
||||||
|
def change_password(user, password, new_password, new_password) do
|
||||||
|
case CommonAPI.Utils.confirm_current_password(user, password) do
|
||||||
|
{:ok, user} ->
|
||||||
|
with {:ok, _user} <-
|
||||||
|
User.reset_password(user, %{
|
||||||
|
password: new_password,
|
||||||
|
password_confirmation: new_password
|
||||||
|
}) do
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def change_password(_, _, _, _), do: {:error, :password_confirmation}
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,4 +39,8 @@ defmodule Pleroma.Web.Auth.WrapperAuthenticator do
|
||||||
implementation().oauth_consumer_template() ||
|
implementation().oauth_consumer_template() ||
|
||||||
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
|
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def change_password(user, password, new_password, new_password_confirmation),
|
||||||
|
do: implementation().change_password(user, password, new_password, new_password_confirmation)
|
||||||
end
|
end
|
||||||
|
|
|
@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||||
def block(blocked, blocker) do
|
def block(blocked, blocker) do
|
||||||
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||||
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||||
|
@ -35,7 +35,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec post_chat_message(User.t(), User.t(), String.t(), list()) ::
|
@spec post_chat_message(User.t(), User.t(), String.t(), list()) ::
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
{:ok, Activity.t()} | Pipeline.errors()
|
||||||
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||||
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||||
:ok <- validate_chat_attachment_attribution(maybe_attachment, user),
|
:ok <- validate_chat_attachment_attribution(maybe_attachment, user),
|
||||||
|
@ -58,7 +58,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
)} do
|
)} do
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:common_pipeline, {:reject, _} = e} -> e
|
{:common_pipeline, e} -> e
|
||||||
e -> e
|
e -> e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -99,7 +99,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unblock(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec unblock(User.t(), User.t()) ::
|
||||||
|
{:ok, Activity.t()} | {:ok, :no_activity} | Pipeline.errors() | {:error, :not_blocking}
|
||||||
def unblock(blocked, blocker) do
|
def unblock(blocked, blocker) do
|
||||||
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)},
|
||||||
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
{:ok, unblock_data, _} <- Builder.undo(blocker, block),
|
||||||
|
@ -120,7 +121,9 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec follow(User.t(), User.t()) ::
|
@spec follow(User.t(), User.t()) ::
|
||||||
{:ok, User.t(), User.t(), Activity.t() | Object.t()} | {:error, :rejected}
|
{:ok, User.t(), User.t(), Activity.t() | Object.t()}
|
||||||
|
| {:error, :rejected}
|
||||||
|
| Pipeline.errors()
|
||||||
def follow(followed, follower) do
|
def follow(followed, follower) do
|
||||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||||
|
|
||||||
|
@ -145,7 +148,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()}
|
@spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors()
|
||||||
def accept_follow_request(follower, followed) do
|
def accept_follow_request(follower, followed) do
|
||||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||||
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
|
{:ok, accept_data, _} <- Builder.accept(followed, follow_activity),
|
||||||
|
@ -154,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} | nil
|
@spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() | nil
|
||||||
def reject_follow_request(follower, followed) do
|
def reject_follow_request(follower, followed) do
|
||||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||||
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
|
{:ok, reject_data, _} <- Builder.reject(followed, follow_activity),
|
||||||
|
@ -163,7 +166,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec delete(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec delete(String.t(), User.t()) ::
|
||||||
|
{:ok, Activity.t()} | Pipeline.errors() | {:error, :not_found | String.t()}
|
||||||
def delete(activity_id, user) do
|
def delete(activity_id, user) do
|
||||||
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
||||||
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
||||||
|
@ -213,7 +217,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, :not_found}
|
||||||
def repeat(id, user, params \\ %{}) do
|
def repeat(id, user, params \\ %{}) do
|
||||||
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
|
||||||
object = %Object{} <- Object.normalize(activity, fetch: false),
|
object = %Object{} <- Object.normalize(activity, fetch: false),
|
||||||
|
@ -231,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, :not_found | String.t()}
|
||||||
def unrepeat(id, user) do
|
def unrepeat(id, user) do
|
||||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||||
{:find_activity, Activity.get_by_id(id)},
|
{:find_activity, Activity.get_by_id(id)},
|
||||||
|
@ -247,7 +251,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec favorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec favorite(String.t(), User.t()) ::
|
||||||
|
{:ok, Activity.t()} | {:ok, :already_liked} | {:error, :not_found | String.t()}
|
||||||
def favorite(id, %User{} = user) do
|
def favorite(id, %User{} = user) do
|
||||||
case favorite_helper(user, id) do
|
case favorite_helper(user, id) do
|
||||||
{:ok, _} = res ->
|
{:ok, _} = res ->
|
||||||
|
@ -285,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unfavorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec unfavorite(String.t(), User.t()) ::
|
||||||
|
{:ok, Activity.t()} | {:error, :not_found | String.t()}
|
||||||
def unfavorite(id, user) do
|
def unfavorite(id, user) do
|
||||||
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
|
||||||
{:find_activity, Activity.get_by_id(id)},
|
{:find_activity, Activity.get_by_id(id)},
|
||||||
|
@ -302,7 +308,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec react_with_emoji(String.t(), User.t(), String.t()) ::
|
@spec react_with_emoji(String.t(), User.t(), String.t()) ::
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
{:ok, Activity.t()} | {:error, String.t()}
|
||||||
def react_with_emoji(id, user, emoji) do
|
def react_with_emoji(id, user, emoji) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
object <- Object.normalize(activity, fetch: false),
|
object <- Object.normalize(activity, fetch: false),
|
||||||
|
@ -316,7 +322,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unreact_with_emoji(String.t(), User.t(), String.t()) ::
|
@spec unreact_with_emoji(String.t(), User.t(), String.t()) ::
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
{:ok, Activity.t()} | {:error, String.t()}
|
||||||
def unreact_with_emoji(id, user, emoji) do
|
def unreact_with_emoji(id, user, emoji) do
|
||||||
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
|
with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji),
|
||||||
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)},
|
{_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)},
|
||||||
|
@ -329,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | {:error, any()}
|
@spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | Pipeline.errors()
|
||||||
def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do
|
def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do
|
||||||
with :ok <- validate_not_author(object, user),
|
with :ok <- validate_not_author(object, user),
|
||||||
:ok <- validate_existing_votes(user, object),
|
:ok <- validate_existing_votes(user, object),
|
||||||
|
@ -461,7 +467,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, nil}
|
||||||
def update(orig_activity, %User{} = user, changes) do
|
def update(orig_activity, %User{} = user, changes) do
|
||||||
with orig_object <- Object.normalize(orig_activity),
|
with orig_object <- Object.normalize(orig_activity),
|
||||||
{:ok, new_object} <- make_update_data(user, orig_object, changes),
|
{:ok, new_object} <- make_update_data(user, orig_object, changes),
|
||||||
|
@ -497,7 +503,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
@spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||||
def pin(id, %User{} = user) do
|
def pin(id, %User{} = user) do
|
||||||
with %Activity{} = activity <- create_activity_by_id(id),
|
with %Activity{} = activity <- create_activity_by_id(id),
|
||||||
true <- activity_belongs_to_actor(activity, user.ap_id),
|
true <- activity_belongs_to_actor(activity, user.ap_id),
|
||||||
|
@ -537,7 +543,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()}
|
@spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors()
|
||||||
def unpin(id, user) do
|
def unpin(id, user) do
|
||||||
with %Activity{} = activity <- create_activity_by_id(id),
|
with %Activity{} = activity <- create_activity_by_id(id),
|
||||||
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
{:ok, unpin_data, _} <- Builder.unpin(user, activity.object),
|
||||||
|
@ -552,7 +558,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, String.t()}
|
||||||
def add_mute(activity, user, params \\ %{}) do
|
def add_mute(activity, user, params \\ %{}) do
|
||||||
expires_in = Map.get(params, :expires_in, 0)
|
expires_in = Map.get(params, :expires_in, 0)
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
websocket: [
|
websocket: [
|
||||||
path: "/",
|
path: "/",
|
||||||
compress: false,
|
compress: false,
|
||||||
|
connect_info: [:sec_websocket_protocol],
|
||||||
error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []},
|
error_handler: {Pleroma.Web.MastodonAPI.WebsocketHandler, :handle_error, []},
|
||||||
fullsweep_after: 20
|
fullsweep_after: 20
|
||||||
]
|
]
|
||||||
|
|
|
@ -46,7 +46,7 @@ defmodule Pleroma.Web.Fallback.RedirectController do
|
||||||
redirector_with_meta(conn, %{user: user})
|
redirector_with_meta(conn, %{user: user})
|
||||||
else
|
else
|
||||||
nil ->
|
nil ->
|
||||||
redirector(conn, params)
|
redirector_with_meta(conn, Map.delete(params, "maybe_nickname_or_id"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -102,7 +102,8 @@ defmodule Pleroma.Web.Federator do
|
||||||
|
|
||||||
# NOTE: we use the actor ID to do the containment, this is fine because an
|
# NOTE: we use the actor ID to do the containment, this is fine because an
|
||||||
# actor shouldn't be acting on objects outside their own AP server.
|
# actor shouldn't be acting on objects outside their own AP server.
|
||||||
with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
|
with {_, {:ok, user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
|
||||||
|
{:user_active, true} <- {:user_active, match?(true, user.is_active)},
|
||||||
nil <- Activity.normalize(params["id"]),
|
nil <- Activity.normalize(params["id"]),
|
||||||
{_, :ok} <-
|
{_, :ok} <-
|
||||||
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
|
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
|
||||||
|
@ -121,11 +122,6 @@ defmodule Pleroma.Web.Federator do
|
||||||
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
|
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
|
||||||
{:error, {:validate_object, _}} = e ->
|
|
||||||
Logger.error("Incoming AP doc validation error: #{inspect(e)}")
|
|
||||||
Logger.debug(Jason.encode!(params, pretty: true))
|
|
||||||
e
|
|
||||||
|
|
||||||
e ->
|
e ->
|
||||||
# Just drop those for now
|
# Just drop those for now
|
||||||
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.Feed.TagController do
|
||||||
alias Pleroma.Web.Feed.FeedView
|
alias Pleroma.Web.Feed.FeedView
|
||||||
|
|
||||||
def feed(conn, params) do
|
def feed(conn, params) do
|
||||||
if Config.get!([:instance, :public]) do
|
if not Config.restrict_unauthenticated_access?(:timelines, :local) do
|
||||||
render_feed(conn, params)
|
render_feed(conn, params)
|
||||||
else
|
else
|
||||||
render_error(conn, :not_found, "Not found")
|
render_error(conn, :not_found, "Not found")
|
||||||
|
@ -18,10 +18,12 @@ defmodule Pleroma.Web.Feed.TagController do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp render_feed(conn, %{"tag" => raw_tag} = params) do
|
defp render_feed(conn, %{"tag" => raw_tag} = params) do
|
||||||
|
local_only = Config.restrict_unauthenticated_access?(:timelines, :federated)
|
||||||
|
|
||||||
{format, tag} = parse_tag(raw_tag)
|
{format, tag} = parse_tag(raw_tag)
|
||||||
|
|
||||||
activities =
|
activities =
|
||||||
%{type: ["Create"], tag: tag}
|
%{type: ["Create"], tag: tag, local_only: local_only}
|
||||||
|> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
|
|> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
|
||||||
|
|
|
@ -15,11 +15,11 @@ defmodule Pleroma.Web.Feed.UserController do
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
|
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname} = params) do
|
||||||
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
|
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
|
||||||
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
|
Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
|
||||||
else
|
else
|
||||||
_ -> Pleroma.Web.Fallback.RedirectController.redirector(conn, nil)
|
_ -> Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -232,6 +232,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
|> Maps.put_if_present(:is_discoverable, params[:discoverable])
|
||||||
|> Maps.put_if_present(:birthday, params[:birthday])
|
|> Maps.put_if_present(:birthday, params[:birthday])
|
||||||
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|
|> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language]))
|
||||||
|
|> Maps.put_if_present(:avatar_description, params[:avatar_description])
|
||||||
|
|> Maps.put_if_present(:header_description, params[:header_description])
|
||||||
|
|
||||||
# What happens here:
|
# What happens here:
|
||||||
#
|
#
|
||||||
|
@ -277,6 +279,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
|
{:error, %Ecto.Changeset{errors: [{:name, {_, _}} | _]}} ->
|
||||||
render_error(conn, :request_entity_too_large, "Name is too long")
|
render_error(conn, :request_entity_too_large, "Name is too long")
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{errors: [{:avatar_description, {_, _}} | _]}} ->
|
||||||
|
render_error(conn, :request_entity_too_large, "Avatar description is too long")
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{errors: [{:header_description, {_, _}} | _]}} ->
|
||||||
|
render_error(conn, :request_entity_too_large, "Banner description is too long")
|
||||||
|
|
||||||
{:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
|
{:error, %Ecto.Changeset{errors: [{:fields, {"invalid", _}} | _]}} ->
|
||||||
render_error(conn, :request_entity_too_large, "One or more field entries are too long")
|
render_error(conn, :request_entity_too_large, "One or more field entries are too long")
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
||||||
|
|
||||||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||||
|
|
||||||
|
plug(Pleroma.Web.Plugs.RateLimiter, [name: :oauth_app_creation] when action == :create)
|
||||||
|
|
||||||
plug(:skip_auth when action in [:create, :verify_credentials])
|
plug(:skip_auth when action in [:create, :verify_credentials])
|
||||||
|
|
||||||
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue