Compare commits
8 commits
develop
...
keys-extra
Author | SHA1 | Date | |
---|---|---|---|
|
18370767c2 | ||
|
5acceec133 | ||
|
161d071f14 | ||
|
4d3f52dcc6 | ||
|
4f9f16587b | ||
|
0d15113b03 | ||
|
9e8675e995 | ||
|
ebc3908fcf |
36 changed files with 733 additions and 370 deletions
|
@ -4,12 +4,12 @@
|
||||||
|
|
||||||
1. Stop the Akkoma service.
|
1. Stop the Akkoma service.
|
||||||
2. Go to the working directory of Akkoma (default is `/opt/akkoma`)
|
2. Go to the working directory of Akkoma (default is `/opt/akkoma`)
|
||||||
3. Run `sudo -Hu postgres pg_dump -d akkoma --format=custom -f </path/to/backup_location/akkoma.pgdump>`[¹] (make sure the postgres user has write access to the destination file)
|
3. Run[¹] `sudo -Hu postgres pg_dump -d akkoma --format=custom -f </path/to/backup_location/akkoma.pgdump>` (make sure the postgres user has write access to the destination file)
|
||||||
4. Copy `akkoma.pgdump`, `config/config.exs`[²], `uploads` folder, and [static directory](../configuration/static_dir.md) to your backup destination. If you have other modifications, copy those changes too.
|
4. Copy `akkoma.pgdump`, `config/prod.secret.exs`[²], `config/setup_db.psql` (if still available) and the `uploads` folder to your backup destination. If you have other modifications, copy those changes too.
|
||||||
5. Restart the Akkoma service.
|
5. Restart the Akkoma service.
|
||||||
|
|
||||||
[¹]: We assume the database name is "akkoma". If not, you can find the correct name in your configuration files.
|
[¹]: We assume the database name is "akkoma". If not, you can find the correct name in your config files.
|
||||||
[²]: If you have a from source installation, you need `config/prod.secret.exs` instead of `config/config.exs`. The `config/config.exs` file also exists, but in case of from source installations, it only contains the default values and it is tracked by Git, so you don't need to back it up.
|
[²]: If you've installed using OTP, you need `config/config.exs` instead of `config/prod.secret.exs`.
|
||||||
|
|
||||||
## Restore/Move
|
## Restore/Move
|
||||||
|
|
||||||
|
@ -17,16 +17,19 @@
|
||||||
2. Stop the Akkoma service.
|
2. Stop the Akkoma service.
|
||||||
3. Go to the working directory of Akkoma (default is `/opt/akkoma`)
|
3. Go to the working directory of Akkoma (default is `/opt/akkoma`)
|
||||||
4. Copy the above mentioned files back to their original position.
|
4. Copy the above mentioned files back to their original position.
|
||||||
5. Drop the existing database and user[¹]. `sudo -Hu postgres psql -c 'DROP DATABASE akkoma;';` `sudo -Hu postgres psql -c 'DROP USER akkoma;'`
|
5. Drop the existing database and user if restoring in-place[¹]. `sudo -Hu postgres psql -c 'DROP DATABASE akkoma;';` `sudo -Hu postgres psql -c 'DROP USER akkoma;'`
|
||||||
6. Restore the database schema and akkoma role[¹] (replace the password with the one you find in the configuration file), `sudo -Hu postgres psql -c "CREATE USER akkoma WITH ENCRYPTED PASSWORD '<database-password-wich-you-can-find-in-your-configuration-file>';"` `sudo -Hu postgres psql -c "CREATE DATABASE akkoma OWNER akkoma;"`.
|
6. Restore the database schema and akkoma role using either of the following options
|
||||||
|
* You can use the original `setup_db.psql` if you have it[²]: `sudo -Hu postgres psql -f config/setup_db.psql`.
|
||||||
|
* Or recreate the database and user yourself (replace the password with the one you find in the config file) `sudo -Hu postgres psql -c "CREATE USER akkoma WITH ENCRYPTED PASSWORD '<database-password-wich-you-can-find-in-your-config-file>'; CREATE DATABASE akkoma OWNER akkoma;"`.
|
||||||
7. Now restore the Akkoma instance's data into the empty database schema[¹]: `sudo -Hu postgres pg_restore -d akkoma -v -1 </path/to/backup_location/akkoma.pgdump>`
|
7. Now restore the Akkoma instance's data into the empty database schema[¹]: `sudo -Hu postgres pg_restore -d akkoma -v -1 </path/to/backup_location/akkoma.pgdump>`
|
||||||
8. If you installed a newer Akkoma version, you should run the database migrations `./bin/pleroma_ctl migrate`[²].
|
8. If you installed a newer Akkoma version, you should run `MIX_ENV=prod mix ecto.migrate`[³]. This task performs database migrations, if there were any.
|
||||||
9. Restart the Akkoma service.
|
9. Restart the Akkoma service.
|
||||||
10. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries.
|
10. Run `sudo -Hu postgres vacuumdb --all --analyze-in-stages`. This will quickly generate the statistics so that postgres can properly plan queries.
|
||||||
11. If setting up on a new server, configure Nginx by using the `installation/nginx/akkoma.nginx` configuration sample or reference the Akkoma installation guide which contains the Nginx configuration instructions.
|
11. If setting up on a new server configure Nginx by using the `installation/akkoma.nginx` config sample or reference the Akkoma installation guide for your OS which contains the Nginx configuration instructions.
|
||||||
|
|
||||||
[¹]: We assume the database name and user are both "akkoma". If not, you can find the correct name in your configuration files.
|
[¹]: We assume the database name and user are both "akkoma". If not, you can find the correct name in your config files.
|
||||||
[²]: If you have a from source installation, the command is `MIX_ENV=prod mix ecto.migrate`. Note that we prefix with `MIX_ENV=prod` to use the `config/prod.secret.exs` configuration file.
|
[²]: You can recreate the `config/setup_db.psql` by running the `mix pleroma.instance gen` task again. You can ignore most of the questions, but make the database user, name, and password the same as found in your backed up config file. This will also create a new `config/generated_config.exs` file which you may delete as it is not needed.
|
||||||
|
[³]: Prefix with `MIX_ENV=prod` to run it using the production config file.
|
||||||
|
|
||||||
## Remove
|
## Remove
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ as soon as the post is received by your instance.
|
||||||
|
|
||||||
## Nginx
|
## Nginx
|
||||||
|
|
||||||
The following are excerpts from the [suggested nginx config](https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/installation/nginx/akkoma.nginx) that demonstrates the necessary config for the media proxy to work.
|
The following are excerpts from the [suggested nginx config](../../../installation/nginx/akkoma.nginx) that demonstrates the necessary config for the media proxy to work.
|
||||||
|
|
||||||
A `proxy_cache_path` must be defined, for example:
|
A `proxy_cache_path` must be defined, for example:
|
||||||
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Keys do
|
|
||||||
# Native generation of RSA keys is only available since OTP 20+ and in default build conditions
|
|
||||||
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
|
|
||||||
try do
|
|
||||||
_ = :public_key.generate_key({:rsa, 2048, 65_537})
|
|
||||||
|
|
||||||
def generate_rsa_pem do
|
|
||||||
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
|
||||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
|
||||||
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
|
|
||||||
{:ok, pem}
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
_ ->
|
|
||||||
def generate_rsa_pem do
|
|
||||||
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
|
||||||
|
|
||||||
{:ok, pem} =
|
|
||||||
receive do
|
|
||||||
{^port, {:data, pem}} -> {:ok, pem}
|
|
||||||
end
|
|
||||||
|
|
||||||
Port.close(port)
|
|
||||||
|
|
||||||
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
|
||||||
{:ok, pem}
|
|
||||||
else
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def keys_from_pem(pem) do
|
|
||||||
with [private_key_code] <- :public_key.pem_decode(pem),
|
|
||||||
private_key <- :public_key.pem_entry_decode(private_key_code),
|
|
||||||
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
|
|
||||||
{:ok, private_key, {:RSAPublicKey, modulus, exponent}}
|
|
||||||
else
|
|
||||||
error -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -339,7 +339,6 @@ defmodule Pleroma.Object.Fetcher do
|
||||||
final_url
|
final_url
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Do NOT use; only public for use in tests"
|
|
||||||
def get_object(id) do
|
def get_object(id) do
|
||||||
date = Pleroma.Signature.signed_date()
|
date = Pleroma.Signature.signed_date()
|
||||||
|
|
||||||
|
|
|
@ -5,47 +5,27 @@
|
||||||
defmodule Pleroma.Signature do
|
defmodule Pleroma.Signature do
|
||||||
@behaviour HTTPSignatures.Adapter
|
@behaviour HTTPSignatures.Adapter
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.Keys
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.User.SigningKey
|
||||||
@known_suffixes ["/publickey", "/main-key", "#key"]
|
require Logger
|
||||||
|
|
||||||
def key_id_to_actor_id(key_id) do
|
def key_id_to_actor_id(key_id) do
|
||||||
uri =
|
# Given the key ID, first attempt to look it up in the signing keys table.
|
||||||
key_id
|
case SigningKey.key_id_to_ap_id(key_id) do
|
||||||
|> URI.parse()
|
nil ->
|
||||||
|> Map.put(:fragment, nil)
|
# hm, we SHOULD have gotten this in the pipeline before we hit here!
|
||||||
|> Map.put(:query, nil)
|
Logger.error("Could not figure out who owns the key #{key_id}")
|
||||||
|> remove_suffix(@known_suffixes)
|
{:error, :key_owner_not_found}
|
||||||
|
|
||||||
maybe_ap_id = URI.to_string(uri)
|
key ->
|
||||||
|
{:ok, key}
|
||||||
case ObjectValidators.ObjectID.cast(maybe_ap_id) do
|
|
||||||
{:ok, ap_id} ->
|
|
||||||
{:ok, ap_id}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
case Pleroma.Web.WebFinger.finger(maybe_ap_id) do
|
|
||||||
{:ok, %{"ap_id" => ap_id}} -> {:ok, ap_id}
|
|
||||||
_ -> {:error, maybe_ap_id}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_suffix(uri, [test | rest]) do
|
|
||||||
if not is_nil(uri.path) and String.ends_with?(uri.path, test) do
|
|
||||||
Map.put(uri, :path, String.replace(uri.path, test, ""))
|
|
||||||
else
|
|
||||||
remove_suffix(uri, rest)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_suffix(uri, []), do: uri
|
|
||||||
|
|
||||||
def fetch_public_key(conn) do
|
def fetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
|
{:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid),
|
||||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
|
@ -57,8 +37,8 @@ defmodule Pleroma.Signature do
|
||||||
|
|
||||||
def refetch_public_key(conn) do
|
def refetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
||||||
|
{:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid),
|
||||||
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
{:ok, actor_id} <- key_id_to_actor_id(kid),
|
||||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
|
@ -67,8 +47,8 @@ defmodule Pleroma.Signature do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign(%User{keys: keys} = user, headers) do
|
def sign(%User{} = user, headers) do
|
||||||
with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
|
with {:ok, private_key} <- SigningKey.private_key(user) do
|
||||||
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,6 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Hashtag
|
alias Pleroma.Hashtag
|
||||||
alias Pleroma.User.HashtagFollow
|
alias Pleroma.User.HashtagFollow
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
alias Pleroma.Keys
|
|
||||||
alias Pleroma.MFA
|
alias Pleroma.MFA
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -43,6 +42,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Web.OAuth
|
alias Pleroma.Web.OAuth
|
||||||
alias Pleroma.Web.RelMe
|
alias Pleroma.Web.RelMe
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
alias Pleroma.User.SigningKey
|
||||||
|
|
||||||
use Pleroma.Web, :verified_routes
|
use Pleroma.Web, :verified_routes
|
||||||
|
|
||||||
|
@ -222,6 +222,10 @@ defmodule Pleroma.User do
|
||||||
on_replace: :delete
|
on_replace: :delete
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# FOR THE FUTURE: We might want to make this a one-to-many relationship
|
||||||
|
# it's entirely possible right now, but we don't have a use case for it
|
||||||
|
has_one(:signing_key, SigningKey, foreign_key: :user_id)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -457,6 +461,7 @@ defmodule Pleroma.User do
|
||||||
|> fix_follower_address()
|
|> fix_follower_address()
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|
|> Repo.preload(:signing_key)
|
||||||
|> cast(
|
|> cast(
|
||||||
params,
|
params,
|
||||||
[
|
[
|
||||||
|
@ -495,6 +500,7 @@ defmodule Pleroma.User do
|
||||||
|> validate_required([:ap_id])
|
|> validate_required([:ap_id])
|
||||||
|> validate_required([:name], trim: false)
|
|> validate_required([:name], trim: false)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
|> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, required: false)
|
||||||
|> validate_format(:nickname, @email_regex)
|
|> validate_format(:nickname, @email_regex)
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: name_limit)
|
|> validate_length(:name, max: name_limit)
|
||||||
|
@ -570,6 +576,7 @@ defmodule Pleroma.User do
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||||
)
|
)
|
||||||
|
|> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, requred: false)
|
||||||
|> validate_fields(false, struct)
|
|> validate_fields(false, struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -828,8 +835,10 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_private_key(changeset) do
|
defp put_private_key(changeset) do
|
||||||
{:ok, pem} = Keys.generate_rsa_pem()
|
ap_id = get_field(changeset, :ap_id)
|
||||||
put_change(changeset, :keys, pem)
|
|
||||||
|
changeset
|
||||||
|
|> put_assoc(:signing_key, SigningKey.generate_local_keys(ap_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp autofollow_users(user) do
|
defp autofollow_users(user) do
|
||||||
|
@ -1146,7 +1155,8 @@ defmodule Pleroma.User do
|
||||||
was_superuser_before_update = User.superuser?(user)
|
was_superuser_before_update = User.superuser?(user)
|
||||||
|
|
||||||
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||||
set_cache(user)
|
user
|
||||||
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|> maybe_remove_report_notifications(was_superuser_before_update)
|
|> maybe_remove_report_notifications(was_superuser_before_update)
|
||||||
end
|
end
|
||||||
|
@ -1624,12 +1634,8 @@ defmodule Pleroma.User do
|
||||||
|
|
||||||
def blocks_user?(_, _), do: false
|
def blocks_user?(_, _), do: false
|
||||||
|
|
||||||
def blocks_domain?(%User{} = user, %User{ap_id: ap_id}) do
|
def blocks_domain?(%User{} = user, %User{} = target) do
|
||||||
blocks_domain?(user, ap_id)
|
%{host: host} = URI.parse(target.ap_id)
|
||||||
end
|
|
||||||
|
|
||||||
def blocks_domain?(%User{} = user, url) when is_binary(url) do
|
|
||||||
%{host: host} = URI.parse(url)
|
|
||||||
Enum.member?(user.domain_blocks, host)
|
Enum.member?(user.domain_blocks, host)
|
||||||
# TODO: functionality should probably be changed such that subdomains block as well,
|
# TODO: functionality should probably be changed such that subdomains block as well,
|
||||||
# but as it stands, this just hecks up the relationships endpoint
|
# but as it stands, this just hecks up the relationships endpoint
|
||||||
|
@ -2051,24 +2057,16 @@ defmodule Pleroma.User do
|
||||||
|> set_cache()
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
|
defdelegate public_key(user), to: SigningKey
|
||||||
key =
|
|
||||||
public_key_pem
|
|
||||||
|> :public_key.pem_decode()
|
|
||||||
|> hd()
|
|
||||||
|> :public_key.pem_entry_decode()
|
|
||||||
|
|
||||||
{:ok, key}
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key(_), do: {:error, "key not found"}
|
|
||||||
|
|
||||||
def get_public_key_for_ap_id(ap_id) do
|
def get_public_key_for_ap_id(ap_id) do
|
||||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||||
{:ok, public_key} <- public_key(user) do
|
{:ok, public_key} <- SigningKey.public_key(user) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
_ -> :error
|
e ->
|
||||||
|
Logger.error("Could not get public key for #{ap_id}.\n#{inspect(e)}")
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
245
lib/pleroma/user/signing_key.ex
Normal file
245
lib/pleroma/user/signing_key.ex
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
defmodule Pleroma.User.SigningKey do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.HTTP
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
schema "signing_keys" do
|
||||||
|
belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType)
|
||||||
|
field :public_key, :string
|
||||||
|
field :private_key, :string
|
||||||
|
# This is an arbitrary field given by the remote instance
|
||||||
|
field :key_id, :string, primary_key: true
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_key(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> Repo.preload(:signing_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def key_id_of_local_user(%User{local: true} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%User{signing_key: %__MODULE__{key_id: key_id}} -> key_id
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec remote_changeset(__MODULE__, map) :: Changeset.t()
|
||||||
|
def remote_changeset(%__MODULE__{} = signing_key, attrs) do
|
||||||
|
signing_key
|
||||||
|
|> cast(attrs, [:public_key, :key_id])
|
||||||
|
|> validate_required([:public_key, :key_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec key_id_to_user_id(String.t()) :: String.t() | nil
|
||||||
|
@doc """
|
||||||
|
Given a key ID, return the user ID associated with that key.
|
||||||
|
Returns nil if the key ID is not found.
|
||||||
|
"""
|
||||||
|
def key_id_to_user_id(key_id) do
|
||||||
|
from(sk in __MODULE__, where: sk.key_id == ^key_id)
|
||||||
|
|> select([sk], sk.user_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec key_id_to_ap_id(String.t()) :: String.t() | nil
|
||||||
|
@doc """
|
||||||
|
Given a key ID, return the AP ID associated with that key.
|
||||||
|
Returns nil if the key ID is not found.
|
||||||
|
"""
|
||||||
|
def key_id_to_ap_id(key_id) do
|
||||||
|
Logger.debug("Looking up key ID: #{key_id}")
|
||||||
|
|
||||||
|
result =
|
||||||
|
from(sk in __MODULE__, where: sk.key_id == ^key_id)
|
||||||
|
|> join(:inner, [sk], u in User, on: sk.user_id == u.id)
|
||||||
|
|> select([sk, u], %{user: u})
|
||||||
|
|> Repo.one()
|
||||||
|
|
||||||
|
case result do
|
||||||
|
%{user: %User{ap_id: ap_id}} -> ap_id
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec generate_rsa_pem() :: {:ok, binary()}
|
||||||
|
@doc """
|
||||||
|
Generate a new RSA private key and return it as a PEM-encoded string.
|
||||||
|
"""
|
||||||
|
def generate_rsa_pem do
|
||||||
|
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
||||||
|
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||||
|
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
|
||||||
|
{:ok, pem}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec generate_local_keys(String.t()) :: {:ok, Changeset.t()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Generate a new RSA key pair and create a changeset for it
|
||||||
|
"""
|
||||||
|
def generate_local_keys(ap_id) do
|
||||||
|
{:ok, private_pem} = generate_rsa_pem()
|
||||||
|
{:ok, local_pem} = private_pem_to_public_pem(private_pem)
|
||||||
|
|
||||||
|
%__MODULE__{}
|
||||||
|
|> change()
|
||||||
|
|> put_change(:public_key, local_pem)
|
||||||
|
|> put_change(:private_key, private_pem)
|
||||||
|
|> put_change(:key_id, ap_id <> "#main-key")
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec private_pem_to_public_pem(binary) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a private key in PEM format, return the corresponding public key in PEM format.
|
||||||
|
"""
|
||||||
|
def private_pem_to_public_pem(private_pem) do
|
||||||
|
[private_key_code] = :public_key.pem_decode(private_pem)
|
||||||
|
private_key = :public_key.pem_entry_decode(private_key_code)
|
||||||
|
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
|
||||||
|
public_key = {:RSAPublicKey, modulus, exponent}
|
||||||
|
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||||
|
{:ok, :public_key.pem_encode([public_key])}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec public_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a user, return the public key for that user in binary format.
|
||||||
|
"""
|
||||||
|
def public_key(%User{} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%User{signing_key: %__MODULE__{public_key: public_key_pem}} ->
|
||||||
|
key =
|
||||||
|
public_key_pem
|
||||||
|
|> :public_key.pem_decode()
|
||||||
|
|> hd()
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
{:ok, key}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "key not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_key(_), do: {:error, "key not found"}
|
||||||
|
|
||||||
|
def public_key_pem(%User{} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%User{signing_key: %__MODULE__{public_key: public_key_pem}} -> {:ok, public_key_pem}
|
||||||
|
_ -> {:error, "key not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_key_pem(e) do
|
||||||
|
{:error, "key not found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a user, return the private key for that user in binary format.
|
||||||
|
"""
|
||||||
|
def private_key(%User{} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%{signing_key: %__MODULE__{private_key: private_key_pem}} ->
|
||||||
|
key =
|
||||||
|
private_key_pem
|
||||||
|
|> :public_key.pem_decode()
|
||||||
|
|> hd()
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
{:ok, key}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "key not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a key ID, return the signing key associated with that key.
|
||||||
|
Will either return the key if it exists locally, or fetch it from the remote instance.
|
||||||
|
"""
|
||||||
|
def get_or_fetch_by_key_id(key_id) do
|
||||||
|
case key_id_to_user_id(key_id) do
|
||||||
|
nil ->
|
||||||
|
fetch_remote_key(key_id)
|
||||||
|
|
||||||
|
user_id ->
|
||||||
|
{:ok, Repo.get_by(__MODULE__, user_id: user_id)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec fetch_remote_key(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Fetch a remote key by key ID.
|
||||||
|
Will send a request to the remote instance to get the key ID.
|
||||||
|
This request should, at the very least, return a user ID and a public key object.
|
||||||
|
Though bear in mind that some implementations (looking at you, pleroma) may require a signature for this request.
|
||||||
|
This has the potential to create an infinite loop if the remote instance requires a signature to fetch the key...
|
||||||
|
So if we're rejected, we should probably just give up.
|
||||||
|
"""
|
||||||
|
def fetch_remote_key(key_id) do
|
||||||
|
Logger.debug("Fetching remote key: #{key_id}")
|
||||||
|
# we should probably sign this, just in case
|
||||||
|
resp = Pleroma.Object.Fetcher.get_object(key_id)
|
||||||
|
|
||||||
|
case resp do
|
||||||
|
{:ok, _original_url, body} ->
|
||||||
|
case handle_signature_response(resp) do
|
||||||
|
{:ok, ap_id, public_key_pem} ->
|
||||||
|
Logger.debug("Fetched remote key: #{ap_id}")
|
||||||
|
# fetch the user
|
||||||
|
{:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
|
||||||
|
# store the key
|
||||||
|
key = %__MODULE__{
|
||||||
|
user_id: user.id,
|
||||||
|
public_key: public_key_pem,
|
||||||
|
key_id: key_id
|
||||||
|
}
|
||||||
|
|
||||||
|
Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id)
|
||||||
|
|
||||||
|
e ->
|
||||||
|
Logger.debug("Failed to fetch remote key: #{inspect(e)}")
|
||||||
|
{:error, "Could not fetch key"}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Logger.debug("Failed to fetch remote key: #{inspect(resp)}")
|
||||||
|
{:error, "Could not fetch key"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Take the response from the remote instance and extract the key details
|
||||||
|
# will check if the key ID matches the owner of the key, if not, error
|
||||||
|
defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do
|
||||||
|
if ap_id !== public_key["owner"] do
|
||||||
|
{:error, "Key ID does not match owner"}
|
||||||
|
else
|
||||||
|
%{"publicKeyPem" => public_key_pem} = public_key
|
||||||
|
{:ok, ap_id, public_key_pem}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_signature_response({:ok, _original_url, body}) do
|
||||||
|
case Jason.decode(body) do
|
||||||
|
{:ok, %{"id" => _user_id, "publicKey" => _public_key} = body} ->
|
||||||
|
extract_key_details(body)
|
||||||
|
|
||||||
|
{:ok, %{"error" => error}} ->
|
||||||
|
{:error, error}
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
{:error, "Could not parse key"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_signature_response({:error, e}), do: {:error, e}
|
||||||
|
defp handle_signature_response(other), do: {:error, "Could not fetch key: #{inspect(other)}"}
|
||||||
|
end
|
|
@ -1547,6 +1547,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
defp normalize_attachment(attachment) when is_list(attachment), do: attachment
|
defp normalize_attachment(attachment) when is_list(attachment), do: attachment
|
||||||
defp normalize_attachment(_), do: []
|
defp normalize_attachment(_), do: []
|
||||||
|
|
||||||
|
defp maybe_make_public_key_object(data) do
|
||||||
|
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
||||||
|
%{
|
||||||
|
public_key: data["publicKey"]["publicKeyPem"],
|
||||||
|
key_id: data["publicKey"]["id"]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp object_to_user_data(data, additional) do
|
defp object_to_user_data(data, additional) do
|
||||||
fields =
|
fields =
|
||||||
data
|
data
|
||||||
|
@ -1578,9 +1589,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
featured_address = data["featured"]
|
featured_address = data["featured"]
|
||||||
{:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
|
{:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
|
||||||
|
|
||||||
public_key =
|
# first, check that the owner is correct
|
||||||
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
signing_key =
|
||||||
data["publicKey"]["publicKeyPem"]
|
if data["id"] !== data["publicKey"]["owner"] do
|
||||||
|
Logger.error(
|
||||||
|
"Owner of the public key is not the same as the actor - not saving the public key."
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
maybe_make_public_key_object(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_inbox =
|
shared_inbox =
|
||||||
|
@ -1624,7 +1642,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||||
bio: data["summary"] || "",
|
bio: data["summary"] || "",
|
||||||
actor_type: actor_type,
|
actor_type: actor_type,
|
||||||
also_known_as: also_known_as,
|
also_known_as: also_known_as,
|
||||||
public_key: public_key,
|
signing_key: signing_key,
|
||||||
inbox: data["inbox"],
|
inbox: data["inbox"],
|
||||||
shared_inbox: shared_inbox,
|
shared_inbox: shared_inbox,
|
||||||
pinned_objects: pinned_objects,
|
pinned_objects: pinned_objects,
|
||||||
|
|
|
@ -60,7 +60,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user(conn, %{"nickname" => nickname}) do
|
@doc """
|
||||||
|
Render the user's AP data
|
||||||
|
WARNING: we cannot actually check if the request has a fragment! so let's play defensively
|
||||||
|
- IF we have a valid signature, serve full user
|
||||||
|
- IF we do not, and authorized_fetch_mode is enabled, serve the key only
|
||||||
|
- OTHERWISE, serve the full actor (since we don't need to worry about the signature)
|
||||||
|
"""
|
||||||
|
def user(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||||
|
render_full_user(conn, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def user(conn, params) do
|
||||||
|
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
||||||
|
render_key_only_user(conn, params)
|
||||||
|
else
|
||||||
|
render_full_user(conn, params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_full_user(conn, %{"nickname" => nickname}) do
|
||||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
@ -72,6 +91,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_key_only_user(conn, %{"nickname" => nickname}) do
|
||||||
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> put_view(UserView)
|
||||||
|
|> render("keys.json", %{user: user})
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
%{local: false} -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def object(%{assigns: assigns} = conn, _) do
|
def object(%{assigns: assigns} = conn, _) do
|
||||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
|
|
|
@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
||||||
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
||||||
|
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Signature
|
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@ -23,8 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
||||||
|
|
||||||
def validate(%{"type" => type, "id" => _id} = data, meta)
|
def validate(%{"type" => type, "id" => _id} = data, meta)
|
||||||
when type in Pleroma.Constants.actor_types() do
|
when type in Pleroma.Constants.actor_types() do
|
||||||
with :ok <- validate_pubkey(data),
|
with :ok <- validate_inbox(data),
|
||||||
:ok <- validate_inbox(data),
|
|
||||||
:ok <- contain_collection_origin(data) do
|
:ok <- contain_collection_origin(data) do
|
||||||
{:ok, data, meta}
|
{:ok, data, meta}
|
||||||
else
|
else
|
||||||
|
@ -35,33 +33,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
||||||
|
|
||||||
def validate(_, _), do: {:error, "Not a user object"}
|
def validate(_, _), do: {:error, "Not a user object"}
|
||||||
|
|
||||||
defp mabye_validate_owner(nil, _actor), do: :ok
|
|
||||||
defp mabye_validate_owner(actor, actor), do: :ok
|
|
||||||
defp mabye_validate_owner(_owner, _actor), do: :error
|
|
||||||
|
|
||||||
defp validate_pubkey(
|
|
||||||
%{"id" => id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}} = data
|
|
||||||
)
|
|
||||||
when id != nil do
|
|
||||||
with {_, {:ok, kactor}} <- {:key, Signature.key_id_to_actor_id(pk_id)},
|
|
||||||
true <- id == kactor,
|
|
||||||
:ok <- mabye_validate_owner(Map.get(data, "owner"), id) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:key, _} ->
|
|
||||||
{:error, "Unable to determine actor id from key id"}
|
|
||||||
|
|
||||||
false ->
|
|
||||||
{:error, "Key id does not relate to user id"}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, "Actor does not own its public key"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# pubkey is optional atm
|
|
||||||
defp validate_pubkey(_data), do: :ok
|
|
||||||
|
|
||||||
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
|
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
|
||||||
case Containment.same_origin(id, inbox) do
|
case Containment.same_origin(id, inbox) do
|
||||||
:ok -> :ok
|
:ok -> :ok
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.UserView do
|
defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
alias Pleroma.Keys
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -33,9 +32,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
def render("endpoints.json", _), do: %{}
|
def render("endpoints.json", _), do: %{}
|
||||||
|
|
||||||
def render("service.json", %{user: user}) do
|
def render("service.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
|
||||||
public_key = :public_key.pem_encode([public_key])
|
|
||||||
|
|
||||||
endpoints = render("endpoints.json", %{user: user})
|
endpoints = render("endpoints.json", %{user: user})
|
||||||
|
|
||||||
|
@ -70,9 +67,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
|
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
|
||||||
|
|
||||||
def render("user.json", %{user: user}) do
|
def render("user.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
public_key =
|
||||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
case User.SigningKey.public_key_pem(user) do
|
||||||
public_key = :public_key.pem_encode([public_key])
|
{:ok, public_key} -> public_key
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
|
||||||
user = User.sanitize_html(user)
|
user = User.sanitize_html(user)
|
||||||
|
|
||||||
endpoints = render("endpoints.json", %{user: user})
|
endpoints = render("endpoints.json", %{user: user})
|
||||||
|
@ -116,6 +116,20 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("keys.json", %{user: user}) do
|
||||||
|
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||||
|
|
||||||
|
%{
|
||||||
|
"id" => user.ap_id,
|
||||||
|
"publicKey" => %{
|
||||||
|
"id" => User.SigningKey.key_id_of_local_user(user),
|
||||||
|
"owner" => user.ap_id,
|
||||||
|
"publicKeyPem" => public_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user, page: page} = opts) do
|
def render("following.json", %{user: user, page: page} = opts) do
|
||||||
showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
|
showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
|
||||||
showing_count = showing_items || !user.hide_follows_count
|
showing_count = showing_items || !user.hide_follows_count
|
||||||
|
|
|
@ -52,14 +52,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp filter_allowed_users_by_domain(ap_ids, %User{} = for_user) do
|
|
||||||
Enum.reject(ap_ids, fn ap_id ->
|
|
||||||
User.blocks_domain?(for_user, ap_id)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp filter_allowed_users_by_domain(ap_ids, nil), do: ap_ids
|
|
||||||
|
|
||||||
def filter_allowed_users(reactions, user, with_muted) do
|
def filter_allowed_users(reactions, user, with_muted) do
|
||||||
exclude_ap_ids =
|
exclude_ap_ids =
|
||||||
if is_nil(user) do
|
if is_nil(user) do
|
||||||
|
@ -70,10 +62,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
|
||||||
end
|
end
|
||||||
|
|
||||||
filter_emoji = fn emoji, users, url ->
|
filter_emoji = fn emoji, users, url ->
|
||||||
users
|
case filter_allowed_user_by_ap_id(users, exclude_ap_ids) do
|
||||||
|> filter_allowed_user_by_ap_id(exclude_ap_ids)
|
|
||||||
|> filter_allowed_users_by_domain(user)
|
|
||||||
|> case do
|
|
||||||
[] -> nil
|
[] -> nil
|
||||||
users -> {emoji, users, url}
|
users -> {emoji, users, url}
|
||||||
end
|
end
|
||||||
|
|
34
lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
Normal file
34
lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do
|
||||||
|
@moduledoc """
|
||||||
|
This plug will attempt to pull in a user's public key if we do not have it.
|
||||||
|
We _should_ be able to request the URL from the key URL...
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def init(options), do: options
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
key_id = key_id_from_conn(conn)
|
||||||
|
|
||||||
|
unless is_nil(key_id) do
|
||||||
|
User.SigningKey.fetch_remote_key(key_id)
|
||||||
|
# now we SHOULD have the user that owns the key locally. maybe.
|
||||||
|
# if we don't, we'll error out when we try to validate.
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
|
defp key_id_from_conn(conn) do
|
||||||
|
case HTTPSignatures.signature_for_conn(conn) do
|
||||||
|
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||||
|
key_id
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -139,12 +139,17 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||||
defp maybe_require_signature(conn), do: conn
|
defp maybe_require_signature(conn), do: conn
|
||||||
|
|
||||||
defp signature_host(conn) do
|
defp signature_host(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)},
|
||||||
{:ok, actor_id} <- Signature.key_id_to_actor_id(kid) do
|
{:actor_id, {:ok, actor_id}} <- {:actor_id, Signature.key_id_to_actor_id(kid)} do
|
||||||
actor_id
|
actor_id
|
||||||
else
|
else
|
||||||
e ->
|
{:key_id, e} ->
|
||||||
{:error, e}
|
Logger.error("Failed to extract key_id from signature: #{inspect(e)}")
|
||||||
|
nil
|
||||||
|
|
||||||
|
{:actor_id, e} ->
|
||||||
|
Logger.error("Failed to extract actor_id from signature: #{inspect(e)}")
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
||||||
alias Pleroma.Helpers.AuthHelper
|
alias Pleroma.Helpers.AuthHelper
|
||||||
alias Pleroma.Signature
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
||||||
|> assign(:valid_signature, false)
|
|> assign(:valid_signature, false)
|
||||||
|
|
||||||
# remove me once testsuite uses mapped capabilities instead of what we do now
|
# remove me once testsuite uses mapped capabilities instead of what we do now
|
||||||
{:user, nil} ->
|
{:user, _} ->
|
||||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
||||||
|
|
||||||
|
@ -93,22 +92,33 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp key_id_from_conn(conn) do
|
defp key_id_from_conn(conn) do
|
||||||
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
|
case HTTPSignatures.signature_for_conn(conn) do
|
||||||
{:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
|
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||||
ap_id
|
key_id
|
||||||
else
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp user_from_key_id(conn) do
|
defp user_from_key_id(conn) do
|
||||||
with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
|
with {:key_id, key_id} when is_binary(key_id) <- {:key_id, key_id_from_conn(conn)},
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
|
{:mapped_ap_id, ap_id} when is_binary(ap_id) <-
|
||||||
|
{:mapped_ap_id, User.SigningKey.key_id_to_ap_id(key_id)},
|
||||||
|
{:user_fetch, {:ok, %User{} = user}} <- {:user_fetch, User.get_or_fetch_by_ap_id(ap_id)} do
|
||||||
user
|
user
|
||||||
else
|
else
|
||||||
_ ->
|
{:key_id, nil} ->
|
||||||
nil
|
Logger.debug("Failed to map identity from signature (no key ID)")
|
||||||
|
{:key_id, nil}
|
||||||
|
|
||||||
|
{:mapped_ap_id, nil} ->
|
||||||
|
Logger.debug("Failed to map identity from signature (could not map key ID to AP ID)")
|
||||||
|
{:mapped_ap_id, nil}
|
||||||
|
|
||||||
|
{:user_fetch, {:error, _}} ->
|
||||||
|
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||||
|
{:user_fetch, nil}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,14 @@ defmodule Pleroma.Web.Router do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pipeline :optional_http_signature do
|
||||||
|
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||||
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
|
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||||
|
end
|
||||||
|
|
||||||
pipeline :http_signature do
|
pipeline :http_signature do
|
||||||
|
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||||
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
||||||
|
@ -745,7 +752,7 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/", Pleroma.Web do
|
scope "/", Pleroma.Web do
|
||||||
# Note: html format is supported only if static FE is enabled
|
# Note: html format is supported only if static FE is enabled
|
||||||
# Note: http signature is only considered for json requests (no auth for non-json requests)
|
# Note: http signature is only considered for json requests (no auth for non-json requests)
|
||||||
pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])
|
pipe_through([:accepts_html_xml_json, :optional_http_signature, :static_fe])
|
||||||
|
|
||||||
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
|
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
|
||||||
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
|
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
|
||||||
|
|
2
mix.exs
2
mix.exs
|
@ -200,7 +200,7 @@ defmodule Pleroma.Mixfile do
|
||||||
|
|
||||||
## dev & test
|
## dev & test
|
||||||
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
|
||||||
{:ex_machina, "~> 2.7", only: :test},
|
{:ex_machina, "~> 2.8", only: :test},
|
||||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||||
{:mock, "~> 0.3.8", only: :test},
|
{:mock, "~> 0.3.8", only: :test},
|
||||||
{:excoveralls, "0.16.1", only: :test},
|
{:excoveralls, "0.16.1", only: :test},
|
||||||
|
|
14
mix.lock
14
mix.lock
|
@ -34,14 +34,14 @@
|
||||||
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
|
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
|
||||||
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
|
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
|
||||||
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
|
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
|
||||||
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
|
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
|
||||||
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
|
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
|
||||||
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
||||||
"ex_aws": {:hex, :ex_aws, "2.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"},
|
"ex_aws": {:hex, :ex_aws, "2.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"},
|
||||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
|
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
|
||||||
"ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
|
"ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"},
|
"ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"},
|
||||||
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
|
"ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
|
||||||
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
|
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
|
||||||
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
|
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
|
||||||
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
|
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
|
||||||
|
@ -83,10 +83,10 @@
|
||||||
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
||||||
"oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [: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", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"},
|
"oban": {:hex, :oban, "2.17.11", "7a641f9f737b626030c3e2209b53df6db83740ac5537208bac7d3b9871c2d5e7", [: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", "c445c488151939d64265a5efea51973fa0b42ee4ebbb31aa83fac26543b8ac6d"},
|
||||||
"open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [: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 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"},
|
"open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [: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 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"},
|
||||||
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
|
||||||
"phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
|
"phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"},
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
|
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
|
||||||
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
|
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
|
||||||
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
|
||||||
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
|
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
|
||||||
"plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
|
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
|
||||||
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
|
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
|
||||||
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
||||||
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
|
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
|
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
|
||||||
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
|
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
|
||||||
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
|
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
|
||||||
"tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"},
|
"tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"},
|
||||||
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
|
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
|
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateSigningKeyTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:signing_keys, primary_key: false) do
|
||||||
|
add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
|
||||||
|
add :key_id, :text, primary_key: true
|
||||||
|
add :public_key, :text
|
||||||
|
add :private_key, :text
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
create unique_index(:signing_keys, [:key_id])
|
||||||
|
end
|
||||||
|
end
|
35
priv/repo/migrations/20240625220752_move_signing_keys.exs
Normal file
35
priv/repo/migrations/20240625220752_move_signing_keys.exs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.MoveSigningKeys do
|
||||||
|
use Ecto.Migration
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def up do
|
||||||
|
# we do not handle remote users here!
|
||||||
|
# because we want to store a key id -> user id mapping, and we don't
|
||||||
|
# currently store key ids for remote users...
|
||||||
|
query =
|
||||||
|
from(u in User)
|
||||||
|
|> where(local: true)
|
||||||
|
|
||||||
|
Repo.stream(query, timeout: :infinity)
|
||||||
|
|> Enum.each(fn
|
||||||
|
%User{id: user_id, keys: private_key, local: true} ->
|
||||||
|
# we can precompute the public key here...
|
||||||
|
# we do use it on every user view which makes it a bit of a dos attack vector
|
||||||
|
# so we should probably cache it
|
||||||
|
{:ok, public_key} = User.SigningKey.private_pem_to_public_pem(private_key)
|
||||||
|
|
||||||
|
key = %User.SigningKey{
|
||||||
|
user_id: user_id,
|
||||||
|
public_key: public_key,
|
||||||
|
private_key: private_key
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, _} = Repo.insert(key)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
# no need to rollback
|
||||||
|
def down, do: :ok
|
||||||
|
end
|
|
@ -401,6 +401,7 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do
|
||||||
["rich_media_card"],
|
["rich_media_card"],
|
||||||
["scheduled_activities"],
|
["scheduled_activities"],
|
||||||
["schema_migrations"],
|
["schema_migrations"],
|
||||||
|
["signing_keys"],
|
||||||
["thread_mutes"],
|
["thread_mutes"],
|
||||||
["user_follows_hashtag"],
|
["user_follows_hashtag"],
|
||||||
["user_frontend_setting_profiles"],
|
["user_frontend_setting_profiles"],
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.KeysTest do
|
|
||||||
use Pleroma.DataCase, async: true
|
|
||||||
|
|
||||||
alias Pleroma.Keys
|
|
||||||
|
|
||||||
test "generates an RSA private key pem" do
|
|
||||||
{:ok, key} = Keys.generate_rsa_pem()
|
|
||||||
|
|
||||||
assert is_binary(key)
|
|
||||||
assert Regex.match?(~r/RSA/, key)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns a public and private key from a pem" do
|
|
||||||
pem = File.read!("test/fixtures/private_key.pem")
|
|
||||||
{:ok, private, public} = Keys.keys_from_pem(pem)
|
|
||||||
|
|
||||||
assert elem(private, 0) == :RSAPrivateKey
|
|
||||||
assert elem(public, 0) == :RSAPublicKey
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -35,25 +35,23 @@ defmodule Pleroma.SignatureTest do
|
||||||
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
|
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
|
||||||
|
|
||||||
describe "fetch_public_key/1" do
|
describe "fetch_public_key/1" do
|
||||||
test "it returns key" do
|
test "it returns the key" do
|
||||||
expected_result = {:ok, @rsa_public_key}
|
expected_result = {:ok, @rsa_public_key}
|
||||||
|
|
||||||
user = insert(:user, public_key: @public_key)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key(public_key: @public_key)
|
||||||
|
|
||||||
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
|
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns error when not found user" do
|
|
||||||
assert capture_log(fn ->
|
|
||||||
assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) ==
|
|
||||||
{:error, :error}
|
|
||||||
end) =~ "[error] Could not decode user"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns error if public key is nil" do
|
test "it returns error if public key is nil" do
|
||||||
user = insert(:user, public_key: nil)
|
# this actually needs the URL to be valid
|
||||||
|
user = insert(:user)
|
||||||
|
key_id = user.ap_id <> "#main-key"
|
||||||
|
Tesla.Mock.mock(fn %{url: ^key_id} -> {:ok, %{status: 404}} end)
|
||||||
|
|
||||||
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
|
assert {:error, _} = Signature.fetch_public_key(make_fake_conn(user.ap_id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,12 +61,6 @@ defmodule Pleroma.SignatureTest do
|
||||||
|
|
||||||
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
|
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns error when not found user" do
|
|
||||||
assert capture_log(fn ->
|
|
||||||
{:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id"))
|
|
||||||
end) =~ "[error] Could not decode user"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp split_signature(sig) do
|
defp split_signature(sig) do
|
||||||
|
@ -104,9 +96,9 @@ defmodule Pleroma.SignatureTest do
|
||||||
test "it returns signature headers" do
|
test "it returns signature headers" do
|
||||||
user =
|
user =
|
||||||
insert(:user, %{
|
insert(:user, %{
|
||||||
ap_id: "https://mastodon.social/users/lambadalambda",
|
ap_id: "https://mastodon.social/users/lambadalambda"
|
||||||
keys: @private_key
|
|
||||||
})
|
})
|
||||||
|
|> with_signing_key(private_key: @private_key)
|
||||||
|
|
||||||
headers = %{
|
headers = %{
|
||||||
host: "test.test",
|
host: "test.test",
|
||||||
|
@ -121,50 +113,15 @@ defmodule Pleroma.SignatureTest do
|
||||||
"keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
|
"keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns error" do
|
|
||||||
user = insert(:user, %{ap_id: "https://mastodon.social/users/lambadalambda", keys: ""})
|
|
||||||
|
|
||||||
assert Signature.sign(
|
|
||||||
user,
|
|
||||||
%{host: "test.test", "content-length": "100"}
|
|
||||||
) == {:error, []}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "key_id_to_actor_id/1" do
|
describe "key_id_to_actor_id/1" do
|
||||||
test "it properly deduces the actor id for misskey" do
|
test "it reverses the key id to actor id" do
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
|
user =
|
||||||
{:ok, "https://example.com/users/1234"}
|
insert(:user)
|
||||||
end
|
|> with_signing_key()
|
||||||
|
|
||||||
test "it properly deduces the actor id for mastodon and pleroma" do
|
assert Signature.key_id_to_actor_id(user.signing_key.key_id) == {:ok, user.ap_id}
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
|
|
||||||
{:ok, "https://example.com/users/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it deduces the actor id for gotoSocial" do
|
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234/main-key") ==
|
|
||||||
{:ok, "https://example.com/users/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it deduces the actor ID for streams" do
|
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234?operation=getkey") ==
|
|
||||||
{:ok, "https://example.com/users/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it deduces the actor ID for bridgy" do
|
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/1234#key") ==
|
|
||||||
{:ok, "https://example.com/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it calls webfinger for 'acct:' accounts" do
|
|
||||||
with_mock(Pleroma.Web.WebFinger,
|
|
||||||
finger: fn _ -> {:ok, %{"ap_id" => "https://gensokyo.2hu/users/raymoo"}} end
|
|
||||||
) do
|
|
||||||
assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") ==
|
|
||||||
{:ok, "https://gensokyo.2hu/users/raymoo"}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,7 @@ defmodule Pleroma.UserSearchTest do
|
||||||
|> Map.put(:multi_factor_authentication_settings, nil)
|
|> Map.put(:multi_factor_authentication_settings, nil)
|
||||||
|> Map.put(:notification_settings, nil)
|
|> Map.put(:notification_settings, nil)
|
||||||
|
|
||||||
assert user == expected
|
assert_user_match(user, expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "excludes a blocked users from search result" do
|
test "excludes a blocked users from search result" do
|
||||||
|
|
|
@ -639,11 +639,12 @@ defmodule Pleroma.UserTest do
|
||||||
changeset = User.register_changeset(%User{}, @full_user_data)
|
changeset = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
|
|
||||||
assert is_binary(changeset.changes[:password_hash])
|
assert is_binary(changeset.changes[:password_hash])
|
||||||
assert is_binary(changeset.changes[:keys])
|
|
||||||
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
||||||
assert is_binary(changeset.changes[:keys])
|
assert changeset.changes[:signing_key]
|
||||||
|
assert changeset.changes[:signing_key].valid?
|
||||||
|
assert is_binary(changeset.changes[:signing_key].changes.private_key)
|
||||||
|
assert is_binary(changeset.changes[:signing_key].changes.public_key)
|
||||||
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1149,18 +1150,6 @@ defmodule Pleroma.UserTest do
|
||||||
assert User.blocks?(user, blocked_user)
|
assert User.blocks?(user, blocked_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it blocks domains" do
|
|
||||||
user = insert(:user)
|
|
||||||
blocked_user = insert(:user)
|
|
||||||
|
|
||||||
refute User.blocks_domain?(user, blocked_user)
|
|
||||||
|
|
||||||
url = URI.parse(blocked_user.ap_id)
|
|
||||||
{:ok, user} = User.block_domain(user, url.host)
|
|
||||||
|
|
||||||
assert User.blocks_domain?(user, blocked_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it unblocks users" do
|
test "it unblocks users" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
blocked_user = insert(:user)
|
blocked_user = insert(:user)
|
||||||
|
@ -1171,17 +1160,6 @@ defmodule Pleroma.UserTest do
|
||||||
refute User.blocks?(user, blocked_user)
|
refute User.blocks?(user, blocked_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it unblocks domains" do
|
|
||||||
user = insert(:user)
|
|
||||||
blocked_user = insert(:user)
|
|
||||||
|
|
||||||
url = URI.parse(blocked_user.ap_id)
|
|
||||||
{:ok, user} = User.block_domain(user, url.host)
|
|
||||||
{:ok, user} = User.unblock_domain(user, url.host)
|
|
||||||
|
|
||||||
refute User.blocks_domain?(user, blocked_user)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "blocks tear down cyclical follow relationships" do
|
test "blocks tear down cyclical follow relationships" do
|
||||||
blocker = insert(:user)
|
blocker = insert(:user)
|
||||||
blocked = insert(:user)
|
blocked = insert(:user)
|
||||||
|
@ -1665,7 +1643,6 @@ defmodule Pleroma.UserTest do
|
||||||
name: "qqqqqqq",
|
name: "qqqqqqq",
|
||||||
password_hash: "pdfk2$1b3n159001",
|
password_hash: "pdfk2$1b3n159001",
|
||||||
keys: "RSA begin buplic key",
|
keys: "RSA begin buplic key",
|
||||||
public_key: "--PRIVATE KEYE--",
|
|
||||||
avatar: %{"a" => "b"},
|
avatar: %{"a" => "b"},
|
||||||
tags: ["qqqqq"],
|
tags: ["qqqqq"],
|
||||||
banner: %{"a" => "b"},
|
banner: %{"a" => "b"},
|
||||||
|
@ -1704,8 +1681,6 @@ defmodule Pleroma.UserTest do
|
||||||
email: nil,
|
email: nil,
|
||||||
name: nil,
|
name: nil,
|
||||||
password_hash: nil,
|
password_hash: nil,
|
||||||
keys: "RSA begin buplic key",
|
|
||||||
public_key: "--PRIVATE KEYE--",
|
|
||||||
avatar: %{},
|
avatar: %{},
|
||||||
tags: [],
|
tags: [],
|
||||||
last_refreshed_at: nil,
|
last_refreshed_at: nil,
|
||||||
|
|
|
@ -584,6 +584,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
local: false,
|
local: false,
|
||||||
last_refreshed_at: nil
|
last_refreshed_at: nil
|
||||||
)
|
)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|
@ -594,7 +595,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{user.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", data)
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
@ -608,7 +609,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||||
|
|
||||||
sender_url = data["actor"]
|
sender_url = data["actor"]
|
||||||
sender = insert(:user, ap_id: data["actor"])
|
|
||||||
|
sender =
|
||||||
|
insert(:user, ap_id: data["actor"])
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
Instances.set_consistently_unreachable(sender_url)
|
Instances.set_consistently_unreachable(sender_url)
|
||||||
refute Instances.reachable?(sender_url)
|
refute Instances.reachable?(sender_url)
|
||||||
|
@ -616,7 +620,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", data)
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
@ -641,7 +645,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert "ok" ==
|
assert "ok" ==
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}#main-key\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", accept)
|
|> post("/inbox", accept)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -678,6 +682,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
|> String.replace("{{nickname}}", "lain")
|
|> String.replace("{{nickname}}", "lain")
|
||||||
|
|
||||||
actor = "https://example.com/users/lain"
|
actor = "https://example.com/users/lain"
|
||||||
|
key_id = "#{actor}/main-key"
|
||||||
|
|
||||||
insert(:user,
|
insert(:user,
|
||||||
ap_id: actor,
|
ap_id: actor,
|
||||||
|
@ -705,6 +710,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^key_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: user,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -778,12 +793,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
|> String.replace("{{nickname}}", "lain")
|
|> String.replace("{{nickname}}", "lain")
|
||||||
|
|
||||||
actor = "https://example.com/users/lain"
|
actor = "https://example.com/users/lain"
|
||||||
|
key_id = "#{actor}/main-key"
|
||||||
|
|
||||||
sender =
|
sender =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
ap_id: actor,
|
ap_id: actor,
|
||||||
featured_address: "https://example.com/users/lain/collections/featured"
|
featured_address: "https://example.com/users/lain/collections/featured"
|
||||||
)
|
)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock(fn
|
||||||
%{
|
%{
|
||||||
|
@ -806,6 +823,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^key_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: user,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -839,7 +866,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert "ok" ==
|
assert "ok" ==
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", data)
|
|> post("/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -901,7 +928,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|
@ -946,7 +975,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
|
test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|
@ -973,7 +1004,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, post} = CommonAPI.post(user, %{status: "hey"})
|
{:ok, post} = CommonAPI.post(user, %{status: "hey"})
|
||||||
announcer = insert(:user, local: false)
|
|
||||||
|
announcer =
|
||||||
|
insert(:user, local: false)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
@ -988,7 +1022,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{announcer.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{announcer.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{user.nickname}/inbox", data)
|
|> post("/users/#{user.nickname}/inbox", data)
|
||||||
|
|
||||||
|
@ -1003,7 +1037,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
data: data
|
data: data
|
||||||
} do
|
} do
|
||||||
recipient = insert(:user)
|
recipient = insert(:user)
|
||||||
actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
|
|
||||||
|
actor =
|
||||||
|
insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
{:ok, recipient, actor} = User.follow(recipient, actor)
|
{:ok, recipient, actor} = User.follow(recipient, actor)
|
||||||
|
|
||||||
|
@ -1019,7 +1056,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{recipient.nickname}/inbox", data)
|
|> post("/users/#{recipient.nickname}/inbox", data)
|
||||||
|
|
||||||
|
@ -1056,7 +1093,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data = Map.put(data, "bcc", [user.ap_id])
|
data = Map.put(data, "bcc", [user.ap_id])
|
||||||
|
|
||||||
sender_host = URI.parse(data["actor"]).host
|
sender_host = URI.parse(data["actor"]).host
|
||||||
|
@ -1066,7 +1106,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{user.nickname}/inbox", data)
|
|> post("/users/#{user.nickname}/inbox", data)
|
||||||
|
|
||||||
|
@ -1077,6 +1117,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
@tag capture_log: true
|
@tag capture_log: true
|
||||||
test "it removes all follower collections but actor's", %{conn: conn} do
|
test "it removes all follower collections but actor's", %{conn: conn} do
|
||||||
[actor, recipient] = insert_pair(:user)
|
[actor, recipient] = insert_pair(:user)
|
||||||
|
actor = with_signing_key(actor)
|
||||||
|
|
||||||
to = [
|
to = [
|
||||||
recipient.ap_id,
|
recipient.ap_id,
|
||||||
|
@ -1105,7 +1146,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{recipient.nickname}/inbox", data)
|
|> post("/users/#{recipient.nickname}/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -1141,7 +1182,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
@tag capture_log: true
|
@tag capture_log: true
|
||||||
test "forwarded report", %{conn: conn} do
|
test "forwarded report", %{conn: conn} do
|
||||||
admin = insert(:user, is_admin: true)
|
admin = insert(:user, is_admin: true)
|
||||||
actor = insert(:user, local: false)
|
|
||||||
|
actor =
|
||||||
|
insert(:user, local: false)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
remote_domain = URI.parse(actor.ap_id).host
|
remote_domain = URI.parse(actor.ap_id).host
|
||||||
reported_user = insert(:user)
|
reported_user = insert(:user)
|
||||||
|
|
||||||
|
@ -1198,7 +1243,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{reported_user.nickname}/inbox", data)
|
|> post("/users/#{reported_user.nickname}/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -1254,7 +1299,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{remote_actor}#main-key\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{reported_user.nickname}/inbox", data)
|
|> post("/users/#{reported_user.nickname}/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
|
@ -140,7 +140,9 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
|
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
assert {:ok, %{body: "port 42"}} =
|
assert {:ok, %{body: "port 42"}} =
|
||||||
Publisher.publish_one(%{
|
Publisher.publish_one(%{
|
||||||
|
@ -165,7 +167,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
@ -176,7 +181,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
|
@ -195,7 +203,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
|
@ -214,7 +225,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://404.site/users/nick1/inbox"
|
inbox = "http://404.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
@ -226,7 +240,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
|
@ -241,7 +258,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
@ -253,7 +273,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
|
@ -294,7 +317,9 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
ap_enabled: true
|
ap_enabled: true
|
||||||
})
|
})
|
||||||
|
|
||||||
actor = insert(:user, follower_address: follower.ap_id)
|
actor =
|
||||||
|
insert(:user, follower_address: follower.ap_id)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
||||||
{:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor)
|
{:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor)
|
||||||
|
@ -365,7 +390,9 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
|
||||||
ap_enabled: true
|
ap_enabled: true
|
||||||
})
|
})
|
||||||
|
|
||||||
actor = insert(:user, follower_address: follower.ap_id)
|
actor =
|
||||||
|
insert(:user, follower_address: follower.ap_id)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
||||||
actor = refresh_record(actor)
|
actor = refresh_record(actor)
|
||||||
|
|
|
@ -33,10 +33,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
third_user = insert(:user)
|
third_user = insert(:user)
|
||||||
domain_blocked_user = insert(:user, %{ap_id: "https://blocked.com/@blocked"})
|
|
||||||
|
|
||||||
{:ok, user} = User.block_domain(user, "blocked.com")
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
||||||
|
@ -44,8 +40,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
|
||||||
# this should not show up when the user is viewing the status
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, domain_blocked_user, "😈")
|
|
||||||
|
|
||||||
activity = Repo.get(Activity, activity.id)
|
activity = Repo.get(Activity, activity.id)
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
@ -61,8 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
url: "http://localhost:4001/emoji/dino walking.gif",
|
url: "http://localhost:4001/emoji/dino walking.gif",
|
||||||
account_ids: [other_user.id, user.id]
|
account_ids: [other_user.id, user.id]
|
||||||
},
|
},
|
||||||
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]},
|
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||||
%{name: "😈", count: 1, me: false, url: nil, account_ids: [domain_blocked_user.id]}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
@ -80,8 +73,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
|
||||||
},
|
},
|
||||||
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||||
]
|
]
|
||||||
|
|
||||||
refute Enum.any?(status[:pleroma][:emoji_reactions], fn reaction -> reaction[:name] == "😈" end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "works correctly with badly formatted emojis" do
|
test "works correctly with badly formatted emojis" do
|
||||||
|
|
|
@ -14,6 +14,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
||||||
import Phoenix.Controller, only: [put_format: 2]
|
import Phoenix.Controller, only: [put_format: 2]
|
||||||
import Mock
|
import Mock
|
||||||
|
|
||||||
|
setup do
|
||||||
|
user =
|
||||||
|
:user
|
||||||
|
|> insert(%{ap_id: "http://mastodon.example.org/users/admin"})
|
||||||
|
|> with_signing_key(%{key_id: "http://mastodon.example.org/users/admin#main-key"})
|
||||||
|
|
||||||
|
{:ok, %{user: user}}
|
||||||
|
end
|
||||||
|
|
||||||
setup_with_mocks([
|
setup_with_mocks([
|
||||||
{HTTPSignatures, [],
|
{HTTPSignatures, [],
|
||||||
[
|
[
|
||||||
|
@ -46,15 +55,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
||||||
|> HTTPSignaturePlug.call(%{})
|
|> HTTPSignaturePlug.call(%{})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it call HTTPSignatures to check validity if the actor signed it" do
|
test "it call HTTPSignatures to check validity if the actor signed it", %{user: user} do
|
||||||
params = %{"actor" => "http://mastodon.example.org/users/admin"}
|
params = %{"actor" => user.ap_id}
|
||||||
conn = build_conn(:get, "/doesntmattter", params)
|
conn = build_conn(:get, "/doesntmattter", params)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header(
|
|> put_req_header(
|
||||||
"signature",
|
"signature",
|
||||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
"keyId=\"#{user.signing_key.key_id}\""
|
||||||
)
|
)
|
||||||
|> put_format("activity+json")
|
|> put_format("activity+json")
|
||||||
|> HTTPSignaturePlug.call(%{})
|
|> HTTPSignaturePlug.call(%{})
|
||||||
|
|
|
@ -8,52 +8,63 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
|
||||||
|
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
import Pleroma.Tests.Helpers, only: [clear_config: 2]
|
import Pleroma.Tests.Helpers, only: [clear_config: 2]
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
:ok
|
|
||||||
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
|
{:ok, %{user: user}}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_signature(conn, key_id) do
|
defp set_signature(conn, ap_id) do
|
||||||
conn
|
conn
|
||||||
|> put_req_header("signature", "keyId=\"#{key_id}\"")
|
|> put_req_header("signature", "keyId=\"#{ap_id}#main-key\"")
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it successfully maps a valid identity with a valid signature" do
|
test "it successfully maps a valid identity with a valid signature", %{user: user} do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:get, "/doesntmattter")
|
build_conn(:get, "/doesntmattter")
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
refute is_nil(conn.assigns.user)
|
refute is_nil(conn.assigns.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it successfully maps a valid identity with a valid signature with payload" do
|
test "it successfully maps a valid identity with a valid signature with payload", %{user: user} do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
refute is_nil(conn.assigns.user)
|
refute is_nil(conn.assigns.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it considers a mapped identity to be invalid when it mismatches a payload" do
|
test "it considers a mapped identity to be invalid when it mismatches a payload", %{user: user} do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("https://niu.moe/users/rye")
|
|> set_signature("https://niu.moe/users/rye")
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it considers a mapped identity to be invalid when the associated instance is blocked" do
|
test "it considers a mapped identity to be invalid when the associated instance is blocked", %{
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
# extract domain from user.ap_id
|
||||||
|
url = URI.parse(user.ap_id)
|
||||||
|
|
||||||
clear_config([:mrf_simple, :reject], [
|
clear_config([:mrf_simple, :reject], [
|
||||||
{"mastodon.example.org", "anime is banned"}
|
{url.host, "anime is banned"}
|
||||||
])
|
])
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
|
@ -62,18 +73,21 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
end
|
end
|
||||||
|
|
||||||
test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed" do
|
test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed",
|
||||||
|
%{user: user} do
|
||||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
url = URI.parse(user.ap_id)
|
||||||
|
|
||||||
clear_config([:mrf_simple, :accept], [
|
clear_config([:mrf_simple, :accept], [
|
||||||
{"mastodon.example.org", "anime is allowed"}
|
{url.host, "anime is allowed"}
|
||||||
])
|
])
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
|
@ -82,15 +96,16 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert conn.assigns[:valid_signature]
|
assert conn.assigns[:valid_signature]
|
||||||
refute is_nil(conn.assigns.user)
|
refute is_nil(conn.assigns.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed" do
|
test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed",
|
||||||
|
%{user: user} do
|
||||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
clear_config([:mrf_simple, :accept], [
|
clear_config([:mrf_simple, :accept], [
|
||||||
|
@ -103,8 +118,8 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
|
|
|
@ -47,7 +47,6 @@ defmodule Pleroma.Factory do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_factory(attrs \\ %{}) do
|
def user_factory(attrs \\ %{}) do
|
||||||
pem = Enum.random(@rsa_keys)
|
|
||||||
# Argon2.hash_pwd_salt("test")
|
# Argon2.hash_pwd_salt("test")
|
||||||
# it really eats CPU time, so we use a precomputed hash
|
# it really eats CPU time, so we use a precomputed hash
|
||||||
password_hash =
|
password_hash =
|
||||||
|
@ -64,8 +63,7 @@ defmodule Pleroma.Factory do
|
||||||
last_refreshed_at: NaiveDateTime.utc_now(),
|
last_refreshed_at: NaiveDateTime.utc_now(),
|
||||||
notification_settings: %Pleroma.User.NotificationSetting{},
|
notification_settings: %Pleroma.User.NotificationSetting{},
|
||||||
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
|
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
|
||||||
ap_enabled: true,
|
ap_enabled: true
|
||||||
keys: pem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
urls =
|
urls =
|
||||||
|
@ -97,6 +95,28 @@ defmodule Pleroma.Factory do
|
||||||
|> merge_attributes(attrs)
|
|> merge_attributes(attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_signing_key(%User{} = user, attrs \\ %{}) do
|
||||||
|
signing_key =
|
||||||
|
build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"})
|
||||||
|
|> merge_attributes(attrs)
|
||||||
|
|
||||||
|
insert(signing_key)
|
||||||
|
%{user | signing_key: signing_key}
|
||||||
|
end
|
||||||
|
|
||||||
|
def signing_key_factory(attrs \\ %{}) do
|
||||||
|
pem = Enum.random(@rsa_keys)
|
||||||
|
user = attrs[:user] || insert(:user)
|
||||||
|
{:ok, public_key} = Pleroma.User.SigningKey.private_pem_to_public_pem(pem)
|
||||||
|
|
||||||
|
%Pleroma.User.SigningKey{
|
||||||
|
user_id: user.id,
|
||||||
|
public_key: attrs[:public_key] || public_key,
|
||||||
|
private_key: attrs[:private_key] || pem,
|
||||||
|
key_id: attrs[:key_id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def user_relationship_factory(attrs \\ %{}) do
|
def user_relationship_factory(attrs \\ %{}) do
|
||||||
source = attrs[:source] || insert(:user)
|
source = attrs[:source] || insert(:user)
|
||||||
target = attrs[:target] || insert(:user)
|
target = attrs[:target] || insert(:user)
|
||||||
|
|
|
@ -66,6 +66,8 @@ defmodule Pleroma.Tests.Helpers do
|
||||||
clear_config: 2
|
clear_config: 2
|
||||||
]
|
]
|
||||||
|
|
||||||
|
import Pleroma.Test.MatchingHelpers
|
||||||
|
|
||||||
def time_travel(entity, seconds) do
|
def time_travel(entity, seconds) do
|
||||||
new_time = NaiveDateTime.add(entity.inserted_at, seconds)
|
new_time = NaiveDateTime.add(entity.inserted_at, seconds)
|
||||||
|
|
||||||
|
|
|
@ -419,6 +419,15 @@ defmodule HttpRequestMock do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("http://mastodon.example.org/users/admin/main-key", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
|
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
|
||||||
_,
|
_,
|
||||||
|
@ -953,6 +962,15 @@ defmodule HttpRequestMock do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/lambadalambda#main-key", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/lambadalambda.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
|
def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -1398,6 +1416,15 @@ defmodule HttpRequestMock do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://relay.mastodon.host/actor#main-key", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/relay/relay.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do
|
def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
|
||||||
end
|
end
|
||||||
|
|
10
test/support/matching_helpers.ex
Normal file
10
test/support/matching_helpers.ex
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule Pleroma.Test.MatchingHelpers do
|
||||||
|
import ExUnit.Assertions
|
||||||
|
|
||||||
|
@assoc_fields [
|
||||||
|
:signing_key
|
||||||
|
]
|
||||||
|
def assert_user_match(actor1, actor2) do
|
||||||
|
assert Ecto.reset_fields(actor1, @assoc_fields) == Ecto.reset_fields(actor2, @assoc_fields)
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue