Merge branch 'fork' into backend-new

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-12 23:57:31 +02:00
commit d580f81f27
130 changed files with 1885 additions and 458 deletions

View file

@ -1,4 +1,4 @@
# Adapter from https://fly.io/phoenix-files/github-actions-for-elixir-ci/
# Adapted from https://fly.io/phoenix-files/github-actions-for-elixir-ci/
name: pl CI
@ -20,7 +20,7 @@ jobs:
test:
services:
db:
image: postgres:12
image: postgres:16
ports: ['5432:5432']
env:
POSTGRES_DB: pleroma_test

View file

@ -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
# Only used for the release
ELIXIR_VER: 1.13.4
ELIXIR_VER: 1.14.5
POSTGRES_DB: pleroma_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
@ -71,7 +71,7 @@ check-changelog:
tags:
- amd64
build-1.13.4-otp-25:
build-1.14.5-otp-25:
extends:
- .build_changes_policy
- .using-ci-base
@ -119,7 +119,7 @@ benchmark:
- mix ecto.migrate
- mix pleroma.load_testing
unit-testing-1.13.4-otp-25:
unit-testing-1.14.5-otp-25:
extends:
- .build_changes_policy
- .using-ci-base
@ -134,7 +134,7 @@ unit-testing-1.13.4-otp-25:
script: &testing_script
- mix ecto.create
- mix ecto.migrate
- mix test --cover --preload-modules
- mix pleroma.test_runner --cover --preload-modules
coverage: '/^Line total: ([^ ]*%)$/'
artifacts:
reports:

View file

@ -1,7 +1,8 @@
# https://hub.docker.com/r/hexpm/elixir/tags
ARG ELIXIR_IMG=hexpm/elixir
ARG ELIXIR_VER=1.13.4
ARG ERLANG_VER=24.3.4.15
ARG ALPINE_VER=3.17.5
ARG ELIXIR_VER=1.14.5
ARG ERLANG_VER=25.3.2.14
ARG ALPINE_VER=3.17.9
FROM ${ELIXIR_IMG}:${ELIXIR_VER}-erlang-${ERLANG_VER}-alpine-${ALPINE_VER} as build

View file

@ -15,18 +15,22 @@ Added features:
- [Partial implementation of Mastodon admin API](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3671)
- [Moderators can assign users to reports](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3670)
- [Mastodon-compatible webhooks](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3683)
- UI is restyled to match pl-fe/Soapbox visuals
- UI is restyled to match pl-fe visuals
- Improved compatibility with akkoma-fe (profiles saving is still missing)
Features not authored by me:
- [Chat deletion](https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3029)
- [AntiDuplicationPolicy, AntiMentionSpamPolicy](https://gitlab.com/soapbox-pub/rebased/-/merge_requests/249), [RemoteReportPolicy](https://gitlab.com/soapbox-pub/rebased/-/merge_requests/202) MRFs
- [AntiDuplicationPolicy and AntiMentionSpamPolicy MRFs](https://gitlab.com/soapbox-pub/rebased/-/merge_requests/249)
- [Bubble timeline](https://akkoma.dev/AkkomaGang/akkoma/pulls/100)
- [Hashtag following](https://akkoma.dev/AkkomaGang/akkoma/pulls/341)
- [Ability to auto-approve followbacks](https://akkoma.dev/AkkomaGang/akkoma/pulls/674)
There might be more, it's hard to keep track of it. I'm trying to keep the fork as close to upstream and I hope the list will eventually get much shorter.
It should be possible to migrate from Pleroma or Rebased to `pl` without issues. It is recommended to use `pl` with [`pl-fe`](https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-fe) for full feature compatibility, but pleroma-fe and other frontends work fine.
**DISCLAIMER:**
Although `pl` *just works* for me, I cannot guarantee that it'll work well for you. There might be bugs I simply don't care about or I might decide to abandon the project one day.
It should be possible to migrate from Pleroma or Rebased to `pl` without issues. It is recommended to use `pl` with [`pl-fe`](https://github.com/mkljczk/pl-fe/tree/develop/packages/pl-fe) for full feature compatibility, but pleroma-fe and other frontends work fine too.
---

View file

@ -0,0 +1 @@
Use `admin.report` for report notification type

View file

@ -0,0 +1 @@
Add instructions for migrating from Akkoma

View file

@ -0,0 +1 @@
Added support for argon2 passwords and their conversion for migration from Akkoma fork to upstream.

View file

@ -0,0 +1 @@
Metadata: Do not include .atom feed links for remote accounts

View file

@ -0,0 +1 @@
Elixir 1.14 and Erlang/OTP 23 is now the minimum supported release

View file

@ -0,0 +1 @@
Repesct :restrict_unauthenticated for hashtag rss/atom feeds

View file

@ -0,0 +1 @@
Fix incoming Block activities being rejected

1
changelog.d/ldap-ca.add Normal file
View file

@ -0,0 +1 @@
LDAP configuration now permits overriding the CA root certificate file for TLS validation.

View file

@ -0,0 +1 @@
LDAP now supports users changing their passwords

View file

@ -0,0 +1 @@
LDAP authentication has been refactored to operate as a GenServer process which will maintain an active connection to the LDAP server.

1
changelog.d/ldap-tls.fix Normal file
View file

@ -0,0 +1 @@
STARTTLS certificate and hostname verification for LDAP authentication

View file

1
changelog.d/ldaps.fix Normal file
View file

@ -0,0 +1 @@
LDAPS connections (implicit TLS) are now supported.

View file

View file

@ -0,0 +1 @@
Add `id_filter` to MRF to filter URLs and their domain prior to fetching

View file

@ -0,0 +1 @@
Add `group_key` to notifications

View file

@ -0,0 +1 @@
Oban updated to 2.18.3

View file

@ -0,0 +1 @@
Poll results refreshing is handled asynchronously and will not attempt to keep fetching updates to a closed poll.

View file

@ -0,0 +1 @@
Added RemoteReportPolicy from Rebased for handling bogus federated reports

View file

@ -0,0 +1 @@
Added dependencies for Swoosh's Mua mail adapter

View file

@ -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 .

View file

@ -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

View file

@ -1,4 +1,4 @@
FROM elixir:1.12.3
FROM elixir:1.14.5-otp-25
# Single RUN statement, otherwise intermediate images are created
# https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run

View file

@ -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 .

View file

@ -351,7 +351,7 @@
icons: [
%{
src: "/static/logo.svg",
sizes: "144x144",
sizes: "512x512",
purpose: "any",
type: "image/svg+xml"
}
@ -625,14 +625,17 @@
config :pleroma, :ldap,
enabled: System.get_env("LDAP_ENABLED") == "true",
host: System.get_env("LDAP_HOST") || "localhost",
port: String.to_integer(System.get_env("LDAP_PORT") || "389"),
host: System.get_env("LDAP_HOST", "localhost"),
port: String.to_integer(System.get_env("LDAP_PORT", "389")),
ssl: System.get_env("LDAP_SSL") == "true",
sslopts: [],
tls: System.get_env("LDAP_TLS") == "true",
tlsopts: [],
base: System.get_env("LDAP_BASE") || "dc=example,dc=com",
uid: System.get_env("LDAP_UID") || "cn"
base: System.get_env("LDAP_BASE", "dc=example,dc=com"),
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 =
System.get_env("OAUTH_CONSUMER_STRATEGIES")
@ -830,6 +833,21 @@
"https://lily-is.land/infra/glitch-lily/-/jobs/artifacts/${ref}/download?job=build",
"ref" => "servant",
"build_dir" => "public"
},
"akkoma-fe" => %{
"name" => "akkoma-fe",
"git" => "https://akkoma.dev/AkkomaGang/akkoma-fe",
"build_url" =>
"https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/akkoma-fe.zip",
"ref" => "develop",
"build_dir" => "dist"
},
"akkoma-admin-fe" => %{
"name" => "akkoma-admin-fe",
"git" => "https://akkoma.dev/AkkomaGang/admin-fe",
"build_url" =>
"https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/admin-fe.zip",
"ref" => "stable"
}
}
@ -963,8 +981,6 @@
config :geospatial, Geospatial.HTTP, user_agent: &Pleroma.Application.user_agent/0
import_config "soapbox.exs"
config :pleroma, Pleroma.Search, module: Pleroma.Search.DatabaseSearch
config :pleroma, Pleroma.Search.Meilisearch,
@ -994,6 +1010,8 @@
vectors: %{size: 384, distance: "Cosine"}
}
import_config "pl-fe.exs"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

View file

@ -2305,14 +2305,8 @@
label: "SSL options",
type: :keyword,
description: "Additional SSL options",
suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
suggestions: [verify: :verify_peer],
children: [
%{
key: :cacertfile,
type: :string,
description: "Path to file with PEM encoded cacerts",
suggestions: ["path/to/file/with/PEM/cacerts"]
},
%{
key: :verify,
type: :atom,
@ -2332,14 +2326,8 @@
label: "TLS options",
type: :keyword,
description: "Additional TLS options",
suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer],
suggestions: [verify: :verify_peer],
children: [
%{
key: :cacertfile,
type: :string,
description: "Path to file with PEM encoded cacerts",
suggestions: ["path/to/file/with/PEM/cacerts"]
},
%{
key: :verify,
type: :atom,
@ -2356,11 +2344,25 @@
},
%{
key: :uid,
label: "UID",
label: "UID Attribute",
type: :string,
description:
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
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"]
}
]
},
@ -3583,6 +3585,19 @@
"translateLocally intermediate language (used when direct source->target model is not available)",
type: :string,
suggestions: ["en"]
},
%{
group: {:subgroup, Pleroma.Language.Translation.Mozhi},
key: :base_url,
label: "Mozhi instance URL",
type: :string
},
%{
group: {:subgroup, Pleroma.Language.Translation.Mozhi},
key: :engine,
label: "Engine used for Mozhi",
type: :string,
suggestions: ["libretranslate"]
}
]
},

View file

@ -1,11 +1,8 @@
# Soapbox default config overrides
# pl-fe default config overrides
# This file gets loaded after config.exs
# and before prod.secret.exs
import Config
# Twitter-like block behavior
config :pleroma, :activitypub, blockers_visible: false
# Sane default upload filters
config :pleroma, Pleroma.Upload,
filters: [
@ -45,12 +42,7 @@
# Sane default media attachment limit
config :pleroma, :instance, max_media_attachments: 20
# Use Soapbox branding
config :pleroma, :instance,
name: "Soapbox",
description: "Social media owned by you",
instance_thumbnail: "/instance/thumbnail.png",
favicon: "/favicon.svg",
account_approval_required: true,
moderator_privileges: [
:users_read,

View file

@ -742,6 +742,21 @@ config :pleroma, Pleroma.Emails.Mailer,
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 settings.
@ -968,12 +983,13 @@ Pleroma account will be created with the same name as the LDAP user name.
* `enabled`: enables LDAP authentication
* `host`: LDAP server hostname
* `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
* `tls`: true to start TLS, usually implies the port 389
* `tls`: true to use explicit TLS (STARTTLS), usually port 389
* `tlsopts`: additional TLS options
* `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"
* `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
OpenLDAP server the value may be `uid: "uid"`.

View file

@ -238,21 +238,12 @@ The `type` value is `pleroma:chat_mention`
- `account`: The account who sent the message
- `chat_message`: The chat message
### Report Notification (not default)
This notification has to be requested explicitly.
The `type` value is `pleroma:report`
- `account`: The account who reported
- `report`: The report
## GET `/api/v1/notifications`
Accepts additional parameters:
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:chat_mention`, `pleroma:report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`, `pleroma:chat_mention`, `admin.report`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
## DELETE `/api/v1/notifications/destroy_multiple`

View file

@ -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-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-nox`
- `git`

View file

@ -1,8 +1,8 @@
## Required dependencies
* PostgreSQL >=11.0
* Elixir >=1.13.0 <1.17
* Erlang OTP >=22.2.0 (supported: <27)
* Elixir >=1.14.0 <1.17
* Erlang OTP >=23.0.0 (supported: <27)
* git
* file / libmagic
* gcc or clang

View file

@ -0,0 +1,17 @@
# Migrating from Akkoma
## Database migration
> Note: You will lose data related about Akkoma-specific features, including: MastoFE settings, user frontend profiles, status auto-expiration config and DM restrictions. Consider taking a backup.
To rollback Akkoma-specific migrations:
- OTP: `./bin/pleroma_ctl rollback --migrations-path priv/repo/optional_migrations/akkoma_rollbacks`
- From Source: `mix ecto.rollback --migrations-path priv/repo/optional_migrations/akkoma_rollbacks`
Then, just
- OTP: `./bin/pleroma_ctl migrate`
- From Source: `mix ecto.migrate`
to apply Pleroma database migrations.

View 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

View 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

View file

@ -97,6 +97,7 @@ def start(_type, _args) do
children =
[
Pleroma.PromEx,
Pleroma.LDAP,
Pleroma.Repo,
Config.TransferTask,
Pleroma.Emoji,

View file

@ -21,7 +21,8 @@ defp reboot_time_keys,
{:pleroma, :markup},
{:pleroma, :streamer},
{:pleroma, :pools},
{:pleroma, :connections_pool}
{:pleroma, :connections_pool},
{:pleroma, :ldap}
]
defp reboot_time_subkeys,

View file

@ -103,6 +103,7 @@ defmodule Pleroma.Constants do
const(activity_types,
do: [
"Block",
"Create",
"Update",
"Delete",
@ -131,6 +132,10 @@ defmodule Pleroma.Constants do
]
)
const(object_types,
do: ~w[Event Question Answer Audio Video Image Article Note Page ChatMessage]
)
# basic regex, just there to weed out potential mistakes
# https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
const(mime_regex,

View file

@ -10,9 +10,9 @@ defmodule Pleroma.Hashtag do
alias Ecto.Multi
alias Pleroma.Hashtag
alias Pleroma.User.HashtagFollow
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User.HashtagFollow
schema "hashtags" do
field(:name, :string)

View file

@ -30,7 +30,7 @@ def translate(content, source_language, target_language) do
text: content,
source_lang: source_language |> String.upcase(),
target_lang: target_language,
tag_handling: "html"
tag_handling: @name
}),
"",
[

View file

@ -46,7 +46,7 @@ def translate(content, source_language, target_language) do
%{
content: content,
detected_source_language: source_language,
provider: "LibreTranslate"
provider: @name
}}
_ ->

View file

@ -0,0 +1,109 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Language.Translation.Mozhi do
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
alias Pleroma.Language.Translation.Provider
use Provider
@behaviour Provider
@name "Mozhi"
@impl Provider
def configured?, do: not_empty_string(base_url()) and not_empty_string(engine())
@impl Provider
def translate(content, source_language, target_language) do
endpoint =
base_url()
|> URI.merge("/api/translate")
|> URI.to_string()
case Pleroma.HTTP.get(
endpoint <>
"?" <>
URI.encode_query(%{
engine: engine(),
text: content,
from: source_language,
to: target_language
}),
[{"Accept", "application/json"}]
) do
{:ok, %{status: 200} = res} ->
%{
"translated-text" => content,
"source_language" => source_language
} = Jason.decode!(res.body)
{:ok,
%{
content: content,
detected_source_language: source_language,
provider: @name
}}
_ ->
{:error, :internal_server_error}
end
end
@impl Provider
def supported_languages(type) when type in [:source, :target] do
path =
case type do
:source -> "/api/source_languages"
:target -> "/api/target_languages"
end
endpoint =
base_url()
|> URI.merge(path)
|> URI.to_string()
case Pleroma.HTTP.get(
endpoint <>
"?" <>
URI.encode_query(%{
engine: engine()
}),
[{"Accept", "application/json"}]
) do
{:ok, %{status: 200} = res} ->
languages =
Jason.decode!(res.body)
|> Enum.map(fn %{"Id" => language} -> language end)
{:ok, languages}
_ ->
{:error, :internal_server_error}
end
end
@impl Provider
def languages_matrix do
with {:ok, source_languages} <- supported_languages(:source),
{:ok, target_languages} <- supported_languages(:target) do
{:ok,
Map.new(source_languages, fn language -> {language, target_languages -- [language]} end)}
else
{:error, error} -> {:error, error}
end
end
@impl Provider
def name, do: @name
defp base_url do
Pleroma.Config.get([__MODULE__, :base_url])
end
defp engine do
Pleroma.Config.get([__MODULE__, :engine])
end
end

271
lib/pleroma/ldap.ex Normal file
View 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

View file

@ -70,7 +70,7 @@ def unread_notifications_count(%User{id: user_id}) do
move
pleroma:chat_mention
pleroma:emoji_reaction
pleroma:report
admin.report
reblog
poll
status
@ -445,7 +445,7 @@ defp type_from_activity(%{data: %{"type" => type}} = activity) do
"pleroma:emoji_reaction"
"Flag" ->
"pleroma:report"
"admin.report"
# Compatibility with old reactions
"EmojiReaction" ->

View file

@ -99,27 +99,6 @@ defp hashtags_changed?(_, _), do: false
def get_by_id(nil), do: nil
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(ap_id) do

View file

@ -146,6 +146,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.debug("Fetching object #{id} via AP")
with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
{_, true} <- {:mrf, MRF.id_filter(id)},
{:ok, body} <- get_object(id),
{:ok, data} <- safe_json_decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do
@ -161,6 +162,9 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
{:error, e} ->
{:error, e}
{:mrf, false} ->
{:error, {:reject, "Filtered by id"}}
e ->
{:error, e}
end

View file

@ -21,7 +21,6 @@ defmodule Pleroma.User do
alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
alias Pleroma.Hashtag
alias Pleroma.User.HashtagFollow
alias Pleroma.HTML
alias Pleroma.Keys
alias Pleroma.MFA
@ -31,6 +30,7 @@ defmodule Pleroma.User do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.User.HashtagFollow
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
@ -440,6 +440,11 @@ def banner_url(user, options \\ []) do
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
@spec ap_id(User.t()) :: String.t()
def ap_id(%User{nickname: nickname}), do: "#{Endpoint.url()}/users/#{nickname}"
@ -1167,7 +1172,7 @@ def needs_update?(_), do: true
# "Locked" (self-locked) users demand explicit authorization of follow requests
@spec can_direct_follow_local(User.t(), User.t()) :: true | false
def can_direct_follow_local(%User{} = follower, %User{local: true} = followed) do
!followed.is_locked || (followed.permit_followback and is_friend_of(follower, followed))
!followed.is_locked || (followed.permit_followback and friend_of?(follower, followed))
end
@spec maybe_direct_follow(User.t(), User.t()) ::
@ -1552,7 +1557,7 @@ def get_familiar_followers(%User{} = user, %User{} = current_user, page \\ nil)
|> Repo.all()
end
def is_friend_of(%User{} = potential_friend, %User{local: true} = user) do
def friend_of?(%User{} = potential_friend, %User{local: true} = user) do
user
|> get_friends_query()
|> where(id: ^potential_friend.id)

View file

@ -3,9 +3,9 @@ defmodule Pleroma.User.HashtagFollow do
import Ecto.Query
import Ecto.Changeset
alias Pleroma.User
alias Pleroma.Hashtag
alias Pleroma.Repo
alias Pleroma.User
schema "user_follows_hashtag" do
belongs_to(:user, User, type: FlakeId.Ecto.CompatType)

View file

@ -1717,16 +1717,23 @@ defp get_actor_url(url) when is_list(url) do
defp get_actor_url(_url), do: nil
defp normalize_image(%{"url" => url}) do
defp normalize_image(%{"url" => url} = data) do
%{
"type" => "Image",
"url" => [%{"href" => url}]
}
|> maybe_put_description(data)
end
defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image()
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
fields =
data

View file

@ -109,6 +109,14 @@ def filter(policies, %{} = message) do
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
def pipeline_filter(%{} = message, meta) do
object = meta[:object_data]

View file

@ -13,6 +13,12 @@ def filter(activity) do
{:reject, activity}
end
@impl true
def id_filter(id) do
Logger.debug("REJECTING #{id}")
false
end
@impl true
def describe, do: {:ok, %{}}
end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.ActivityPub.MRF.Policy do
@callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()}
@callback id_filter(String.t()) :: boolean()
@callback describe() :: {:ok | :error, map()}
@callback config_description() :: %{
optional(:children) => [map()],
@ -13,5 +14,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.Policy do
description: String.t()
}
@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

View file

@ -9,6 +9,7 @@ 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
@ -37,6 +38,22 @@ defp maybe_reject_anonymous(%{"actor" => actor} = object) do
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}
@ -83,6 +100,12 @@ def config_description do
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,

View file

@ -191,6 +191,18 @@ defp instance_list(config_key) do
|> MRF.instance_list_from_tuples()
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
def filter(%{"type" => "Delete", "actor" => actor} = activity) do
%{host: actor_host} = URI.parse(actor)

View file

@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
import Pleroma.Constants, only: [activity_types: 0, object_types: 0]
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
@ -42,6 +44,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@impl true
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
with {:ok, block_activity} <-
block_activity

View file

@ -1021,6 +1021,7 @@ defp replace_instance_host(value, nil), do: value
defp replace_instance_host(content, host) when is_binary(content) do
content
|> String.replace("$INSTANCE$host$", host)
|> String.replace("$INSTANCE$tld$", get_tld(host, "$INSTANCE$tld$"))
end
defp replace_instance_host(object, host) when is_map(object) do
@ -1032,6 +1033,14 @@ defp replace_instance_host(object, host) when is_map(object) do
defp replace_instance_host(value, _), do: value
defp get_tld(host, default) do
with [domain | _] <- String.split(host, ".") |> Enum.reverse() do
domain
else
_ -> default
end
end
defp patch_content_map(%{"contentMap" => %{} = content_map}, host) do
content_map
|> Enum.map(fn {key, value} -> {key, replace_instance_host(value, host)} end)

View file

@ -132,8 +132,22 @@ def render("user.json", %{user: user}) do
"webfinger" => "acct:#{User.full_nickname(user)}",
"vcard:Address" => user.location
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|> Map.merge(
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())
end
@ -314,16 +328,24 @@ def collection(collection, iri, page, show_items \\ true, total \\ nil) do
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
%{
key => %{
"type" => "Image",
"url" => image
}
key =>
%{
"type" => "Image",
"url" => image
}
|> maybe_put_description(description)
}
else
%{}
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

View file

@ -0,0 +1,100 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AkkomaCompatController do
use Pleroma.Web, :controller
alias Pleroma.Activity
alias Pleroma.Language.Translation
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(:skip_auth when action == :translation_languages)
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:statuses"]} when action == :translate
)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AkkomaCompatOperation
@doc "GET /api/v1/akkoma/translation/languages"
def translation_languages(conn, _params) do
with {:enabled, true} <- {:enabled, Translation.configured?()},
{:ok, source_languages} <- Translation.supported_languages(:source),
{:ok, target_languages} <- Translation.supported_languages(:target) do
source_languages =
source_languages
|> Enum.map(fn lang -> %{code: lang, name: lang} end)
target_languages =
target_languages
|> Enum.map(fn lang -> %{code: lang, name: lang} end)
conn
|> json(%{source: source_languages, target: target_languages})
else
{:enabled, false} ->
json(conn, %{})
e ->
{:error, e}
end
end
@doc "GET /api/v1/statuses/:id/translations/:language"
def translate(
%{
assigns: %{user: user},
private: %{open_api_spex: %{params: %{id: status_id} = params}}
} = conn,
_
) do
with {:authentication, true} <-
{:authentication,
!is_nil(user) ||
Pleroma.Config.get([Translation, :allow_unauthenticated])},
%Activity{object: object} <- Activity.get_by_id_with_object(status_id),
{:visibility, visibility} when visibility in ["public", "unlisted"] <-
{:visibility, Visibility.get_visibility(object)},
{:allow_remote, true} <-
{:allow_remote,
Object.local?(object) ||
Pleroma.Config.get([Translation, :allow_remote])},
{:language, language} when is_binary(language) <-
{:language, Map.get(params, :language) || user.language},
{:ok, result} <-
Translation.translate(
object.data["content"],
object.data["language"],
language
) do
json(conn, %{detected_language: result.detected_source_language, text: result.content})
else
{:authentication, false} ->
render_error(conn, :unauthorized, "Authorization is required to translate statuses")
{:allow_remote, false} ->
render_error(conn, :bad_request, "You can't translate remote posts")
{:language, nil} ->
render_error(conn, :bad_request, "Language not specified")
{:visibility, _} ->
render_error(conn, :not_found, "Record not found")
{:error, :not_found} ->
render_error(conn, :not_found, "Translation service not configured")
{:error, error} when error in [:unexpected_response, :quota_exceeded, :too_many_requests] ->
render_error(conn, :service_unavailable, "Translation service not available")
nil ->
render_error(conn, :not_found, "Record not found")
end
end
end

View file

@ -0,0 +1,91 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.AkkomaCompatOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
# Adapted from https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/api_spec/operations/translate_operation.ex
def translation_languages_operation do
%Operation{
tags: ["Akkoma compatibility routes"],
summary: "Get translation languages",
description: "Retreieve a list of supported source and target language",
operationId: "AkkomaCompatController.translation_languages",
responses: %{
200 =>
Operation.response(
"Translation languages",
"application/json",
source_dest_languages_schema()
)
}
}
end
defp source_dest_languages_schema do
%Schema{
type: :object,
required: [:source, :target],
properties: %{
source: languages_schema(),
target: languages_schema()
}
}
end
defp languages_schema do
%Schema{
type: :array,
items: %Schema{
type: :object,
properties: %{
code: %Schema{type: :string},
name: %Schema{type: :string}
}
}
}
end
def translate_operation do
%Operation{
tags: ["Akkoma compatibility routes"],
summary: "Translate status",
description: "Translate status with an external API",
operationId: "AkkomaCompatController.translate",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [
Operation.parameter(:id, :path, FlakeID.schema(), "Status ID",
example: "9umDrYheeY451cQnEe",
required: true
),
Operation.parameter(:language, :path, :string, "Target language code", example: "en"),
Operation.parameter(:from, :query, :string, "Source language code (unused)",
example: "en"
)
],
responses: %{
200 =>
Operation.response(
"Translated status",
"application/json",
%Schema{
type: :object,
required: [:detected_language, :text],
properties: %{
detected_language: %Schema{type: :string},
text: %Schema{type: :string}
}
}
)
}
}
end
end

View file

@ -157,6 +157,10 @@ def notification do
type: :object,
properties: %{
id: %Schema{type: :string},
group_key: %Schema{
type: :string,
description: "Group key shared by similar notifications"
},
type: notification_type(),
created_at: %Schema{type: :string, format: :"date-time"},
account: %Schema{
@ -184,6 +188,7 @@ def notification do
},
example: %{
"id" => "34975861",
"group-key" => "ungrouped-34975861",
"type" => "mention",
"created_at" => "2019-11-23T07:49:02.064Z",
"account" => Account.schema().example,
@ -203,7 +208,6 @@ defp notification_type do
"mention",
"pleroma:emoji_reaction",
"pleroma:chat_mention",
"pleroma:report",
"move",
"follow_request",
"poll",
@ -229,7 +233,6 @@ defp notification_type do
- `move` - Someone moved their account
- `pleroma:emoji_reaction` - Someone reacted with emoji to your status
- `pleroma:chat_mention` - Someone mentioned you in a chat message
- `pleroma:report` - Someone was reported
- `status` - Someone you are subscribed to created a status
- `update` - A status you boosted has been edited
- `pleroma:event_reminder` An event you are participating in or created is taking place soon

View file

@ -10,4 +10,9 @@ defmodule Pleroma.Web.Auth.Authenticator do
@callback handle_error(Plug.Conn.t(), any()) :: any()
@callback auth_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

View file

@ -3,18 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.LDAPAuthenticator do
alias Pleroma.LDAP
alias Pleroma.User
require Logger
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1]
@behaviour Pleroma.Web.Auth.Authenticator
@base Pleroma.Web.Auth.PleromaAuthenticator
@connection_timeout 10_000
@search_timeout 10_000
defdelegate get_registration(conn), to: @base
defdelegate create_from_registration(conn, registration), 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
with {:ldap, true} <- {:ldap, Pleroma.Config.get([:ldap, :enabled])},
{:ok, {name, password}} <- fetch_credentials(conn),
%User{} = user <- ldap_user(name, password) do
%User{} = user <- LDAP.bind_user(name, password) do
{:ok, user}
else
{:ldap, _} ->
@ -35,106 +31,12 @@ def get_user(%Plug.Conn{} = conn) do
end
end
defp ldap_user(name, password) do
ldap = Pleroma.Config.get(:ldap, [])
host = Keyword.get(ldap, :host, "localhost")
port = Keyword.get(ldap, :port, 389)
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
bind_user(connection, ldap, name, password)
after
:eldap.close(connection)
end
{:error, error} ->
Logger.error("Could not open LDAP connection: #{inspect(error)}")
{:error, {:ldap_connection_error, error}}
def change_password(user, password, new_password, new_password) do
case LDAP.change_password(user.nickname, password, new_password) do
:ok -> {:ok, user}
e -> e
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
def change_password(_, _, _, _), do: {:error, :password_confirmation}
end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.AuthenticationPlug
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
@ -101,4 +102,23 @@ def handle_error(%Plug.Conn{} = _conn, error) do
def auth_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

View file

@ -39,4 +39,8 @@ def oauth_consumer_template do
implementation().oauth_consumer_template() ||
Pleroma.Config.get([:auth, :oauth_consumer_template], "consumer.html")
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

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Web.Feed.TagController do
alias Pleroma.Web.Feed.FeedView
def feed(conn, params) do
if Config.get!([:instance, :public]) do
if not Config.restrict_unauthenticated_access?(:timelines, :local) do
render_feed(conn, params)
else
render_error(conn, :not_found, "Not found")
@ -18,10 +18,12 @@ def feed(conn, params) do
end
defp render_feed(conn, %{"tag" => raw_tag} = params) do
local_only = Config.restrict_unauthenticated_access?(:timelines, :federated)
{format, tag} = parse_tag(raw_tag)
activities =
%{type: ["Create"], tag: tag}
%{type: ["Create"], tag: tag, local_only: local_only}
|> Pleroma.Maps.put_if_present(:max_id, params["max_id"])
|> ActivityPub.fetch_public_activities()

View file

@ -8,10 +8,10 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2]
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Pagination
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
plug(:assign_follower when action != :index)

View file

@ -12,6 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Workers.PollWorker
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@ -27,12 +28,16 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PollOperation
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@poll_refresh_interval 120
@doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn, _) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
with %Object{} = object <- Object.get_by_id(id),
%Activity{} = activity <-
Activity.get_create_by_object_ap_id_with_object(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do
maybe_refresh_poll(activity)
try_render(conn, "show.json", %{object: object, for: user})
else
error when is_nil(error) or error == false ->
@ -70,4 +75,13 @@ defp get_cached_vote_or_vote(object, user, choices) do
end
end)
end
defp maybe_refresh_poll(%Activity{object: %Object{} = object} = activity) do
with false <- activity.local,
{:ok, end_time} <- NaiveDateTime.from_iso8601(object.data["closed"]),
{_, :lt} <- {:closed_compare, NaiveDateTime.compare(object.updated_at, end_time)} do
PollWorker.new(%{"op" => "refresh", "activity_id" => activity.id})
|> Oban.insert(unique: [period: @poll_refresh_interval])
end
end
end

View file

@ -2,9 +2,9 @@ defmodule Pleroma.Web.MastodonAPI.TagController do
@moduledoc "Hashtag routes for mastodon API"
use Pleroma.Web, :controller
alias Pleroma.User
alias Pleroma.Hashtag
alias Pleroma.Pagination
alias Pleroma.User
import Pleroma.Web.ControllerHelper,
only: [

View file

@ -65,14 +65,14 @@ def get_notifications(user, params \\ %{}) do
cast_params(params) |> Map.update(:include_types, [], fn include_types -> include_types end)
options =
if ("pleroma:report" not in options.include_types and
if ("admin.report" not in options.include_types and
User.privileged?(user, :reports_manage_reports)) or
User.privileged?(user, :reports_manage_reports) do
options
else
options
|> Map.update(:exclude_types, ["pleroma:report"], fn current_exclude_types ->
current_exclude_types ++ ["pleroma:report"]
|> Map.update(:exclude_types, ["admin.report"], fn current_exclude_types ->
current_exclude_types ++ ["admin.report"]
end)
end

View file

@ -219,10 +219,10 @@ defp do_render("show.json", %{user: user} = opts) do
avatar = User.avatar_url(user) |> MediaProxy.url()
avatar_static = User.avatar_url(user) |> MediaProxy.preview_url(static: true)
avatar_description = image_description(user.avatar)
avatar_description = User.image_description(user.avatar)
header = User.banner_url(user) |> MediaProxy.url()
header_static = User.banner_url(user) |> MediaProxy.preview_url(static: true)
header_description = image_description(user.banner)
header_description = User.image_description(user.banner)
following_count =
if !user.hide_follows_count or !user.hide_follows or self,
@ -352,10 +352,6 @@ defp username_from_nickname(string) when is_binary(string) do
defp username_from_nickname(_), do: nil
defp image_description(%{"name" => name}), do: name
defp image_description(_), do: ""
defp maybe_put_follow_requests_count(
data,
%User{id: user_id} = user,

View file

@ -78,6 +78,11 @@ def render("show2.json", _) do
email: Keyword.get(instance, :email),
account: contact_account(Keyword.get(instance, :contact_username))
},
api_versions: %{
"mastodon" => 2137,
"social.pleroma" => 420,
"pl.mkljczk.pl" => 69
},
# Extra (not present in Mastodon):
pleroma: pleroma_configuration2(instance)
# soapbox: %{
@ -186,6 +191,13 @@ def features do
"events",
"multitenancy",
"pleroma:bites",
if !Enum.empty?(Config.get([:instance, :local_bubble], [])) do
"bubble_timeline"
end,
# Akkoma compatibility
if Pleroma.Language.Translation.configured?() do
"akkoma:machine_translation"
end,
"pleroma:multi_language",
"bigbuffet"
]

View file

@ -95,6 +95,7 @@ def render(
response = %{
id: to_string(notification.id),
group_key: "ungrouped-" <> to_string(notification.id),
type: notification.type,
created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
account: account,
@ -131,7 +132,7 @@ def render(
"pleroma:chat_mention" ->
put_chat_message(response, activity, reading_user, status_render_opts)
"pleroma:report" ->
"admin.report" ->
put_report(response, activity)
"pleroma:participation_accepted" ->

View file

@ -10,7 +10,7 @@ defmodule Pleroma.Web.Metadata.Providers.Feed do
@behaviour Provider
@impl Provider
def build_tags(%{user: user}) do
def build_tags(%{user: %{local: true} = user}) do
[
{:link,
[
@ -20,4 +20,6 @@ def build_tags(%{user: user}) do
], []}
]
end
def build_tags(_), do: []
end

View file

@ -75,6 +75,7 @@ def get_nodeinfo("2.0") do
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false),
federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true),
localBubbleInstances: Config.get([:instance, :local_bubble], []),
publicTimelineVisibility: %{
federated:
!Config.restrict_unauthenticated_access?(:timelines, :federated) &&

View file

@ -47,6 +47,11 @@ def checkpw(password, "$pbkdf2" <> _ = password_hash) do
Pleroma.Password.Pbkdf2.verify_pass(password, password_hash)
end
def checkpw(password, "$argon2" <> _ = password_hash) do
# Handle argon2 passwords for Akkoma migration
Argon2.verify_pass(password, password_hash)
end
def checkpw(_password, _password_hash) do
Logger.error("Password hash not recognized")
false
@ -56,6 +61,10 @@ def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(%User{password_hash: "$argon2" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(user, _), do: {:ok, user}
defp do_update_password(user, password) do

View file

@ -92,7 +92,7 @@ defp csp_string do
static_url = Pleroma.Web.Endpoint.static_url()
websocket_url = Pleroma.Web.Endpoint.websocket_url()
report_uri = @config_impl.get([:http_security, :report_uri])
sentry_dsn = @config_impl.get([:frontend_configurations, :soapbox_fe, "sentryDsn"])
sentry_dsn = @config_impl.get([:frontend_configurations, :pl_fe, "sentryDsn"])
img_src = "img-src 'self' data: blob:"
media_src = "media-src 'self'"
@ -200,7 +200,7 @@ defp build_csp_multimedia_source_list do
defp map_tile_server do
with tile_server when is_binary(tile_server) <-
@config_impl.get([:frontend_configurations, :soapbox_fe, "tileServer"]),
@config_impl.get([:frontend_configurations, :pl_fe, "tileServer"]),
%{host: host} <- URI.parse(tile_server) do
["*.#{host}"]
else

View file

@ -731,6 +731,13 @@ defmodule Pleroma.Web.Router do
end
end
scope "/", Pleroma.Web do
pipe_through(:api)
get("/api/v1/akkoma/translation/languages", AkkomaCompatController, :translation_languages)
get("/api/v1/statuses/:id/translations/:language", AkkomaCompatController, :translate)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)

View file

@ -9,13 +9,13 @@
:root {
<%= Pleroma.Web.Utils.Colors.shades_to_css(
"primary",
Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "brandColor"], "#0482d8"),
Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "colors", "primary"], %{})
Pleroma.Config.get([:frontend_configurations, :pl_fe, "brandColor"], "#d80482"),
Pleroma.Config.get([:frontend_configurations, :pl_fe, "colors", "primary"], %{})
) %>
<%= Pleroma.Web.Utils.Colors.shades_to_css(
"accent",
Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "accentColor"], "#2bd110"),
Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "colors", "accent"], %{})
Pleroma.Config.get([:frontend_configurations, :pl_fe, "accentColor"], "#d110b4"),
Pleroma.Config.get([:frontend_configurations, :pl_fe, "colors", "accent"], %{})
) %>
}
</style>
@ -23,7 +23,7 @@
<body>
<div class="instance-header">
<a class="instance-header__content" href="/">
<img class="instance-header__thumbnail" src="<%= Pleroma.Config.get([:frontend_configurations, :soapbox_fe, "logo"], Pleroma.Config.get([:instance, :instance_thumbnail])) %>">
<img class="instance-header__thumbnail" src="<%= Pleroma.Config.get([:frontend_configurations, :pl_fe, "logo"], Pleroma.Config.get([:instance, :instance_thumbnail])) %>">
<h1 class="instance-header__title"><%= Pleroma.Config.get([:instance, :name]) %></h1>
</a>
</div>

View file

@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Healthcheck
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
@ -195,19 +196,21 @@ def change_password(
%{assigns: %{user: user}, private: %{open_api_spex: %{body_params: body_params}}} = conn,
_
) do
case CommonAPI.Utils.confirm_current_password(user, body_params.password) do
{:ok, user} ->
with {:ok, _user} <-
User.reset_password(user, %{
password: body_params.new_password,
password_confirmation: body_params.new_password_confirmation
}) do
json(conn, %{status: "success"})
else
{:error, changeset} ->
{_, {error, _}} = Enum.at(changeset.errors, 0)
json(conn, %{error: "New password #{error}."})
end
with {:ok, %User{}} <-
Authenticator.change_password(
user,
body_params.password,
body_params.new_password,
body_params.new_password_confirmation
) do
json(conn, %{status: "success"})
else
{:error, %Ecto.Changeset{} = changeset} ->
{_, {error, _}} = Enum.at(changeset.errors, 0)
json(conn, %{error: "New password #{error}."})
{:error, :password_confirmation} ->
json(conn, %{error: "New password does not match confirmation."})
{:error, msg} ->
json(conn, %{error: msg})

View file

@ -51,7 +51,7 @@ def get_shades("#" <> base_color, overrides) do
shades
end
def get_shades(_, overrides), do: get_shades("#0482d8", overrides)
def get_shades(_, overrides), do: get_shades("#d80482", overrides)
defp get_override(level, overrides) do
if Map.has_key?(overrides, "#{level}") do

View file

@ -12,9 +12,14 @@ def render("manifest.json", _params) do
name: Config.get([:instance, :name]),
description: Config.get([:instance, :description]),
icons: Config.get([:manifest, :icons]),
theme_color: Config.get([:manifest, :theme_color]),
theme_color:
Config.get(
[:frontend_configurations, :pl_fe, "brandColor"],
Config.get([:manifest, :theme_color])
),
background_color: Config.get([:manifest, :background_color]),
display: "standalone",
display_override: ["window-controls-overlay"],
scope: Endpoint.url(),
start_url: "/",
categories: [

View file

@ -11,27 +11,46 @@ defmodule Pleroma.Workers.PollWorker do
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Object.Fetcher
@stream_out_impl Pleroma.Config.get(
[__MODULE__, :stream_out],
Pleroma.Web.ActivityPub.ActivityPub
)
@impl true
def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do
with %Activity{} = activity <- find_poll_activity(activity_id),
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)},
{:ok, notifications} <- Notification.create_poll_notifications(activity) do
unless activity.local do
# Schedule a final refresh
__MODULE__.new(%{"op" => "refresh", "activity_id" => activity_id})
|> Oban.insert()
end
Notification.stream(notifications)
else
{:error, :poll_activity_not_found} = e -> {:cancel, e}
{:activity, nil} -> {:cancel, :poll_activity_not_found}
e -> {:error, e}
end
end
def perform(%Job{args: %{"op" => "refresh", "activity_id" => activity_id}}) do
with {_, %Activity{object: object}} <-
{:activity, Activity.get_by_id_with_object(activity_id)},
{_, {:ok, _object}} <- {:refetch, Fetcher.refetch_object(object)} do
stream_update(activity_id)
:ok
else
{:activity, nil} -> {:cancel, :poll_activity_not_found}
{:refetch, _} = e -> {:cancel, e}
end
end
@impl true
def timeout(_job), do: :timer.seconds(5)
defp find_poll_activity(activity_id) do
with nil <- Activity.get_by_id(activity_id) do
{:error, :poll_activity_not_found}
end
end
def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} = activity) do
with %Object{data: %{"type" => "Question", "closed" => closed}} when is_binary(closed) <-
Object.normalize(activity),
@ -49,4 +68,10 @@ def schedule_poll_end(%Activity{data: %{"type" => "Create"}, id: activity_id} =
end
def schedule_poll_end(activity), do: {:error, activity}
defp stream_update(activity_id) do
Activity.get_by_id(activity_id)
|> Activity.normalize()
|> @stream_out_impl.stream_out()
end
end

View file

@ -9,7 +9,7 @@ def project do
name: "pl",
compat_name: "Pleroma",
version: version("2.7.0"),
elixir: "~> 1.13",
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors(), prune_code_paths: false],
@ -156,7 +156,7 @@ defp deps do
{:cachex, "~> 3.2"},
{:csv, "~> 2.4"},
{:tesla, "~> 1.11"},
{:castore, "~> 0.1"},
{:castore, "~> 1.0"},
{:cowlib, "~> 2.9", override: true},
{:gun, "~> 2.0.0-rc.1", override: true},
{:finch, "~> 0.15"},
@ -172,6 +172,8 @@ defp deps do
{:swoosh, "~> 1.16.9"},
{:phoenix_swoosh, "~> 1.1"},
{:gen_smtp, "~> 0.13"},
{:mua, "~> 0.2.0"},
{:mail, "~> 0.3.0"},
{:ex_syslogger, "~> 1.4"},
{:floki, "~> 0.35"},
{:timex, "~> 3.6"},
@ -208,6 +210,7 @@ defp deps do
{:multipart, "~> 0.4.0", optional: true},
{:icalendar, "~> 1.1"},
{:geospatial, "~> 0.3.1"},
{:argon2_elixir, "~> 4.0"},
## dev & test
{:phoenix_live_reload, "~> 1.3.3", only: :dev},

View file

@ -1,5 +1,6 @@
%{
"accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"},
"argon2_elixir": {:hex, :argon2_elixir, "4.0.0", "7f6cd2e4a93a37f61d58a367d82f830ad9527082ff3c820b8197a8a736648941", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f9da27cf060c9ea61b1bd47837a28d7e48a8f6fa13a745e252556c14f9132c7f"},
"bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"},
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
@ -10,7 +11,7 @@
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.5.20 or ~> 0.1.201603 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
"castore": {:hex, :castore, "0.1.22", "4127549e411bedd012ca3a308dede574f43819fe9394254ca55ab4895abfa1a2", [:mix], [], "hexpm", "c17576df47eb5aa1ee40cc4134316a99f5cad3e215d5c77b8dd3cfef12a22cac"},
"castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
@ -79,6 +80,7 @@
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"linkify": {:git, "https://gitlab.com/mkljczk/linkify", "b854b2f1701a5e13dfea50add729a8525a549f64", [branch: "master"]},
"logger_backends": {:hex, :logger_backends, "1.0.0", "09c4fad6202e08cb0fbd37f328282f16539aca380f512523ce9472b28edc6bdf", [:mix], [], "hexpm", "1faceb3e7ec3ef66a8f5746c5afd020e63996df6fd4eb8cdb789e5665ae6c9ce"},
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
"majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
@ -92,6 +94,7 @@
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
"mua": {:hex, :mua, "0.2.3", "46b29b7b2bb14105c0b7be9526f7c452df17a7841b30b69871c024a822ff551c", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "7fe861a87fcc06a980d3941bbcb2634e5f0f30fd6ad15ef6c0423ff9dc7e46de"},
"multipart": {:hex, :multipart, "0.4.0", "634880a2148d4555d050963373d0e3bbb44a55b2badd87fa8623166172e9cda0", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "3c5604bc2fb17b3137e5d2abdf5dacc2647e60c5cc6634b102cf1aef75a06f0a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
@ -99,7 +102,7 @@
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oauth2": {:hex, :oauth2, "0.9.4", "632e8e8826a45e33ac2ea5ac66dcc019ba6bb5a0d2ba77e342d33e3b7b252c6e", [:mix], [{:hackney, "~> 1.7", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "407c6b9f60aa0d01b915e2347dc6be78adca706a37f0c530808942da3b62e7af"},
"oauther": {:hex, :oauther, "1.3.0", "82b399607f0ca9d01c640438b34d74ebd9e4acd716508f868e864537ecdb1f76", [:mix], [], "hexpm", "78eb888ea875c72ca27b0864a6f550bc6ee84f2eeca37b093d3d833fbcaec04e"},
"oban": {:hex, :oban, "2.18.2", "583e78965ee15263ac968e38c983bad169ae55eadaa8e1e39912562badff93ba", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9dd25fd35883a91ed995e9fe516e479344d3a8623dfe2b8c3fc8e5be0228ec3a"},
"oban": {:hex, :oban, "2.18.3", "1608c04f8856c108555c379f2f56bc0759149d35fa9d3b825cb8a6769f8ae926", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "36ca6ca84ef6518f9c2c759ea88efd438a3c81d667ba23b02b062a0aa785475e"},
"oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"},
"octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"},
"open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"},

View file

@ -7,8 +7,8 @@ defmodule Pleroma.Repo.Migrations.InstancesAddMetadata do
def change do
alter table(:instances) do
add(:metadata, :map)
add(:metadata_updated_at, :utc_datetime)
add_if_not_exists(:metadata, :map)
add_if_not_exists(:metadata_updated_at, :utc_datetime)
end
end
end

View file

@ -0,0 +1,101 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Repo.Migrations.RenamePleromaReportNotificationTypeToAdminReport do
use Ecto.Migration
def up do
alter table(:notifications) do
modify(:type, :string)
end
"""
update notifications
set type = 'admin.report'
where type = 'pleroma:report'
"""
|> execute()
"""
drop type if exists notification_type
"""
|> execute()
"""
create type notification_type as enum (
'follow',
'follow_request',
'mention',
'move',
'pleroma:emoji_reaction',
'pleroma:chat_mention',
'reblog',
'favourite',
'admin.report',
'poll',
'status',
'update',
'pleroma:participation_accepted',
'pleroma:participation_request',
'pleroma:event_reminder',
'pleroma:event_update',
'bite'
)
"""
|> execute()
"""
alter table notifications
alter column type type notification_type using (type::notification_type)
"""
|> execute()
end
def down do
alter table(:notifications) do
modify(:type, :string)
end
"""
update notifications
set type = 'pleroma:report'
where type = 'admin.report'
"""
|> execute()
"""
drop type if exists notification_type
"""
|> execute()
"""
create type notification_type as enum (
'follow',
'follow_request',
'mention',
'move',
'pleroma:emoji_reaction',
'pleroma:chat_mention',
'reblog',
'favourite',
'pleroma:report',
'poll',
'status',
'update',
'pleroma:participation_accepted',
'pleroma:participation_request',
'pleroma:event_reminder',
'pleroma:event_update',
'bite'
)
"""
|> execute()
"""
alter table notifications
alter column type type notification_type using (type::notification_type)
"""
|> execute()
end
end

View file

@ -0,0 +1,24 @@
# Adapted from Akkoma
# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20220108213213_add_mastofe_settings.exs
defmodule Pleroma.Repo.Migrations.AddMastofeSettings do
use Ecto.Migration
def up, do: :ok
def down do
"""
UPDATE users SET pleroma_settings_store = jsonb_set(
pleroma_settings_store,
'{glitch-lily}',
mastofe_settings
)
WHERE mastofe_settings IS NOT NULL;
"""
|> execute()
alter table(:users) do
remove_if_exists(:mastofe_settings, :map)
end
end
end

View file

@ -0,0 +1,5 @@
defmodule Pleroma.Repo.Migrations.UpgradeObanToV11 do
use Ecto.Migration
def change, do: :ok
end

View file

@ -0,0 +1,5 @@
defmodule Pleroma.Repo.Migrations.RemoveRemoteCancelledFollowRequests do
use Ecto.Migration
def change, do: :ok
end

View file

@ -0,0 +1,5 @@
defmodule Pleroma.Repo.Migrations.RemoveLocalCancelledFollows do
use Ecto.Migration
def change, do: :ok
end

View file

@ -0,0 +1,17 @@
# Adapted from Akkoma
# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs
defmodule Pleroma.Repo.Migrations.AddUserFrontendProfiles do
use Ecto.Migration
def up, do: :ok
def down do
drop_if_exists(table("user_frontend_setting_profiles"))
drop_if_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name]))
drop_if_exists(
unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name])
)
end
end

View file

@ -0,0 +1,14 @@
# Adapted from Akkoma
# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs
defmodule Pleroma.Repo.Migrations.EnsureMastofeSettings do
use Ecto.Migration
def up, do: :ok
def down do
alter table(:users) do
remove_if_exists(:mastofe_settings, :map)
end
end
end

View file

@ -0,0 +1,15 @@
# Adapted from Akkoma
# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221020135943_add_nodeinfo.exs
defmodule Pleroma.Repo.Migrations.AddNodeinfo do
use Ecto.Migration
def up, do: :ok
def down do
alter table(:instances) do
remove_if_exists(:nodeinfo, :map)
remove_if_exists(:metadata_updated_at, :naive_datetime)
end
end
end

View file

@ -0,0 +1,12 @@
# Adapted from Akkoma
# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221123221956_add_has_request_signatures.exs
defmodule Pleroma.Repo.Migrations.AddHasRequestSignatures do
use Ecto.Migration
def change do
alter table(:instances) do
add(:has_request_signatures, :boolean, default: false, null: false)
end
end
end

View file

@ -0,0 +1,12 @@
# Adapted from Akkoma
# https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/priv/repo/migrations/20221128103145_add_per_user_post_expiry.exs
defmodule Pleroma.Repo.Migrations.AddPerUserPostExpiry do
use Ecto.Migration
def change do
alter table(:users) do
add(:status_ttl_days, :integer, null: true)
end
end
end

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