commit
e7ac15905e
@ -0,0 +1,59 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(message) do
|
||||||
|
with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
|
||||||
|
%User{actor_type: "Service"} = follower <-
|
||||||
|
User.get_cached_by_nickname(follower_nickname),
|
||||||
|
%{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
|
||||||
|
try_follow(follower, message)
|
||||||
|
else
|
||||||
|
nil ->
|
||||||
|
Logger.warn(
|
||||||
|
"#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname
|
||||||
|
account does not exist, or the account is not correctly configured as a bot."
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp try_follow(follower, message) do
|
||||||
|
to = Map.get(message, "to", [])
|
||||||
|
cc = Map.get(message, "cc", [])
|
||||||
|
actor = [message["actor"]]
|
||||||
|
|
||||||
|
Enum.concat([to, cc, actor])
|
||||||
|
|> List.flatten()
|
||||||
|
|> Enum.uniq()
|
||||||
|
|> User.get_all_by_ap_id()
|
||||||
|
|> Enum.each(fn user ->
|
||||||
|
with false <- user.local,
|
||||||
|
false <- User.following?(follower, user),
|
||||||
|
false <- User.locked?(user),
|
||||||
|
false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
|
||||||
|
Logger.debug(
|
||||||
|
"#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
|
||||||
|
)
|
||||||
|
|
||||||
|
CommonAPI.follow(follower, user)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, message}
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe do
|
||||||
|
{:ok, %{}}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,77 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:target)
|
||||||
|
field(:object, ObjectValidators.ObjectID)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:type)
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
{:ok, actor} = User.get_or_fetch_by_ap_id(data["actor"])
|
||||||
|
|
||||||
|
{:ok, actor} = maybe_refetch_user(actor)
|
||||||
|
|
||||||
|
data
|
||||||
|
|> maybe_fix_data_for_mastodon(actor)
|
||||||
|
|> cast_data()
|
||||||
|
|> validate_data(actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_fix_data_for_mastodon(data, actor) do
|
||||||
|
# Mastodon sends pin/unpin objects without id, to, cc fields
|
||||||
|
data
|
||||||
|
|> Map.put_new("id", Pleroma.Web.ActivityPub.Utils.generate_activity_id())
|
||||||
|
|> Map.put_new("to", [Pleroma.Constants.as_public()])
|
||||||
|
|> Map.put_new("cc", [actor.follower_address])
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cast_data(data) do
|
||||||
|
cast(%__MODULE__{}, data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_data(changeset, actor) do
|
||||||
|
changeset
|
||||||
|
|> validate_required([:id, :target, :object, :actor, :type, :to, :cc])
|
||||||
|
|> validate_inclusion(:type, ~w(Add Remove))
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_collection_belongs_to_actor(actor)
|
||||||
|
|> validate_object_presence()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_collection_belongs_to_actor(changeset, actor) do
|
||||||
|
validate_change(changeset, :target, fn :target, target ->
|
||||||
|
if target == actor.featured_address do
|
||||||
|
[]
|
||||||
|
else
|
||||||
|
[target: "collection doesn't belong to actor"]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(address) do
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_refetch_user(%User{ap_id: ap_id}) do
|
||||||
|
Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,77 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.TagValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
embedded_schema do
|
||||||
|
# Common
|
||||||
|
field(:type, :string)
|
||||||
|
field(:name, :string)
|
||||||
|
|
||||||
|
# Mention, Hashtag
|
||||||
|
field(:href, ObjectValidators.Uri)
|
||||||
|
|
||||||
|
# Emoji
|
||||||
|
embeds_one :icon, IconObjectValidator, primary_key: false do
|
||||||
|
field(:type, :string)
|
||||||
|
field(:url, ObjectValidators.Uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
field(:updated, ObjectValidators.DateTime)
|
||||||
|
field(:id, ObjectValidators.Uri)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> changeset(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Mention"} = data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :href])
|
||||||
|
|> validate_required([:type, :href])
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Hashtag", "name" => name} = data) do
|
||||||
|
name =
|
||||||
|
cond do
|
||||||
|
"#" <> name -> name
|
||||||
|
name -> name
|
||||||
|
end
|
||||||
|
|> String.downcase()
|
||||||
|
|
||||||
|
data = Map.put(data, "name", name)
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :href])
|
||||||
|
|> validate_required([:type, :name])
|
||||||
|
end
|
||||||
|
|
||||||
|
def changeset(struct, %{"type" => "Emoji"} = data) do
|
||||||
|
data = Map.put(data, "name", String.trim(data["name"], ":"))
|
||||||
|
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :name, :updated, :id])
|
||||||
|
|> cast_embed(:icon, with: &icon_changeset/2)
|
||||||
|
|> validate_required([:type, :name, :icon])
|
||||||
|
end
|
||||||
|
|
||||||
|
def icon_changeset(struct, data) do
|
||||||
|
struct
|
||||||
|
|> cast(data, [:type, :url])
|
||||||
|
|> validate_inclusion(:type, ~w[Image])
|
||||||
|
|> validate_required([:type, :url])
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,9 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.AddPinnedObjectsToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add(:pinned_objects, :map)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,23 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.AddFeaturedAddressToUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:users) do
|
||||||
|
add(:featured_address, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
create(index(:users, [:featured_address]))
|
||||||
|
|
||||||
|
execute("""
|
||||||
|
|
||||||
|
update users set featured_address = concat(ap_id, '/collections/featured') where local = true and featured_address is null;
|
||||||
|
|
||||||
|
""")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
remove(:featured_address)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,28 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.MovePinnedActivitiesIntoPinnedObjects do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def up do
|
||||||
|
from(u in User)
|
||||||
|
|> select([u], {u.id, fragment("?.pinned_activities", u)})
|
||||||
|
|> Repo.stream()
|
||||||
|
|> Stream.each(fn {user_id, pinned_activities_ids} ->
|
||||||
|
pinned_activities = Pleroma.Activity.all_by_ids_with_object(pinned_activities_ids)
|
||||||
|
|
||||||
|
pins =
|
||||||
|
Map.new(pinned_activities, fn %{object: %{data: %{"id" => object_id}}} ->
|
||||||
|
{object_id, NaiveDateTime.utc_now()}
|
||||||
|
end)
|
||||||
|
|
||||||
|
from(u in User, where: u.id == ^user_id)
|
||||||
|
|> Repo.update_all(set: [pinned_objects: pins])
|
||||||
|
end)
|
||||||
|
|> Stream.run()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down, do: :noop
|
||||||
|
end
|
@ -0,0 +1,15 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.RemovePinnedActivitiesFromUsers do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
alter table(:users) do
|
||||||
|
remove(:pinned_activities)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
alter table(:users) do
|
||||||
|
add(:pinned_activities, {:array, :string}, default: [])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,17 @@
|
|||||||
|
defmodule Pleroma.Repo.Migrations.UserNotificationSettingsFix do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute(~s(UPDATE users
|
||||||
|
SET
|
||||||
|
notification_settings = '{"followers": true, "follows": true, "non_follows": true, "non_followers": true}'::jsonb WHERE notification_settings IS NULL
|
||||||
|
))
|
||||||
|
|
||||||
|
execute("ALTER TABLE users
|
||||||
|
ALTER COLUMN notification_settings SET NOT NULL")
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,5 @@
|
|||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
config :pleroma, exported_config_merged: true
|
||||||
|
|
||||||
|
config :pleroma, :first_setting, key: "new value"
|
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://{{domain}}/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://{{domain}}/users/{{nickname}}/collections/featured",
|
||||||
|
"orderedItems": [
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://{{domain}}/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actor": "https://{{domain}}/users/{{nickname}}",
|
||||||
|
"attachment": [],
|
||||||
|
"attributedTo": "https://{{domain}}/users/{{nickname}}",
|
||||||
|
"cc": [
|
||||||
|
"https://{{domain}}/users/{{nickname}}/followers"
|
||||||
|
],
|
||||||
|
"content": "",
|
||||||
|
"id": "https://{{domain}}/objects/{{object_id}}",
|
||||||
|
"published": "2021-02-12T15:13:43.915429Z",
|
||||||
|
"sensitive": false,
|
||||||
|
"source": "",
|
||||||
|
"summary": "",
|
||||||
|
"tag": [],
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"type": "Note"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "OrderedCollection"
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
{
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"votersCount": "toot:votersCount"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
|
||||||
|
"type": "Note",
|
||||||
|
"summary": null,
|
||||||
|
"inReplyTo": null,
|
||||||
|
"published": "2021-02-24T12:40:49Z",
|
||||||
|
"url": "https://example.com/@{{nickname}}/{{status_id}}",
|
||||||
|
"attributedTo": "https://example.com/users/{{nickname}}",
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"cc": [
|
||||||
|
"https://example.com/users/{{nickname}}/followers"
|
||||||
|
],
|
||||||
|
"sensitive": false,
|
||||||
|
"atomUri": "https://example.com/users/{{nickname}}/statuses/{{status_id}}",
|
||||||
|
"inReplyToAtomUri": null,
|
||||||
|
"conversation": "tag:example.com,2021-02-24:objectId=15:objectType=Conversation",
|
||||||
|
"content": "<p></p>",
|
||||||
|
"contentMap": {
|
||||||
|
"en": "<p></p>"
|
||||||
|
},
|
||||||
|
"attachment": [],
|
||||||
|
"tag": [],
|
||||||
|
"replies": {
|
||||||
|
"id": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
|
||||||
|
"type": "Collection",
|
||||||
|
"first": {
|
||||||
|
"type": "CollectionPage",
|
||||||
|
"next": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies?only_other_accounts=true&page=true",
|
||||||
|
"partOf": "https://example.com/users/{{nickname}}/statuses/{{status_id}}/replies",
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://example.com/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"actor": "https://example.com/users/{{nickname}}",
|
||||||
|
"attachment": [],
|
||||||
|
"attributedTo": "https://example.com/users/{{nickname}}",
|
||||||
|
"cc": [
|
||||||
|
"https://example.com/users/{{nickname}}/followers"
|
||||||
|
],
|
||||||
|
"content": "Content",
|
||||||
|
"context": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f",
|
||||||
|
"conversation": "https://example.com/contexts/e4b180e1-7403-477f-aeb4-de57e7a3fe7f",
|
||||||
|
"id": "https://example.com/objects/{{object_id}}",
|
||||||
|
"published": "2019-12-15T22:00:05.279583Z",
|
||||||
|
"sensitive": false,
|
||||||
|
"summary": "",
|
||||||
|
"tag": [],
|
||||||
|
"to": [
|
||||||
|
"https://www.w3.org/ns/activitystreams#Public"
|
||||||
|
],
|
||||||
|
"type": "Note"
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
{
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"votersCount": "toot:votersCount"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://{{domain}}/users/{{nickname}}/collections/featured",
|
||||||
|
"type": "OrderedCollection",
|
||||||
|
"totalItems": 0,
|
||||||
|
"orderedItems": []
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
"https://example.com/schemas/litepub-0.1.jsonld",
|
||||||
|
{
|
||||||
|
"@language": "und"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attachment": [],
|
||||||
|
"endpoints": {
|
||||||
|
"oauthAuthorizationEndpoint": "https://example.com/oauth/authorize",
|
||||||
|
"oauthRegistrationEndpoint": "https://example.com/api/v1/apps",
|
||||||
|
"oauthTokenEndpoint": "https://example.com/oauth/token",
|
||||||
|
"sharedInbox": "https://example.com/inbox"
|
||||||
|
},
|
||||||
|
"followers": "https://example.com/users/{{nickname}}/followers",
|
||||||
|
"following": "https://example.com/users/{{nickname}}/following",
|
||||||
|
"icon": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://example.com/media/4e914f5b84e4a259a3f6c2d2edc9ab642f2ab05f3e3d9c52c81fc2d984b3d51e.jpg"
|
||||||
|
},
|
||||||
|
"id": "https://example.com/users/{{nickname}}",
|
||||||
|
"image": {
|
||||||
|
"type": "Image",
|
||||||
|
"url": "https://example.com/media/f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg?name=f739efddefeee49c6e67e947c4811fdc911785c16ae43da4c3684051fbf8da6a.jpg"
|
||||||
|
},
|
||||||
|
"inbox": "https://example.com/users/{{nickname}}/inbox",
|
||||||
|
"manuallyApprovesFollowers": false,
|
||||||
|
"name": "{{nickname}}",
|
||||||
|
"outbox": "https://example.com/users/{{nickname}}/outbox",
|
||||||
|
"preferredUsername": "{{nickname}}",
|
||||||
|
"publicKey": {
|
||||||
|
"id": "https://example.com/users/{{nickname}}#main-key",
|
||||||
|
"owner": "https://example.com/users/{{nickname}}",
|
||||||
|
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5DLtwGXNZElJyxFGfcVc\nXANhaMadj/iYYQwZjOJTV9QsbtiNBeIK54PJrYuU0/0YIdrvS1iqheX5IwXRhcwa\nhm3ZyLz7XeN9st7FBni4BmZMBtMpxAuYuu5p/jbWy13qAiYOhPreCx0wrWgm/lBD\n9mkgaxIxPooBE0S4ZWEJIDIV1Vft3AWcRUyWW1vIBK0uZzs6GYshbQZB952S0yo4\nFzI1hABGHncH8UvuFauh4EZ8tY7/X5I0pGRnDOcRN1dAht5w5yTA+6r5kebiFQjP\nIzN/eCO/a9Flrj9YGW7HDNtjSOH0A31PLRGlJtJO3yK57dnf5ppyCZGfL4emShQo\ncQIDAQAB\n-----END PUBLIC KEY-----\n\n"
|
||||||
|
},
|
||||||
|
"featured": "https://example.com/users/{{nickname}}/collections/featured",
|
||||||
|
"summary": "your friendly neighborhood pleroma developer<br>I like cute things and distributed systems, and really hate delete and redrafts",
|
||||||
|
"tag": [],
|
||||||
|
"type": "Person",
|
||||||
|
"url": "https://example.com/users/{{nickname}}"
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
defmodule Pleroma.Config.ReleaseRuntimeProviderTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Pleroma.Config.ReleaseRuntimeProvider
|
||||||
|
|
||||||
|
describe "load/2" do
|
||||||
|
test "loads release defaults config and warns about non-existent runtime config" do
|
||||||
|
ExUnit.CaptureIO.capture_io(fn ->
|
||||||
|
merged = ReleaseRuntimeProvider.load([], [])
|
||||||
|
assert merged == Pleroma.Config.Holder.release_defaults()
|
||||||
|
end) =~
|
||||||
|
"!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "merged runtime config" do
|
||||||
|
merged =
|
||||||
|
ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs")
|
||||||
|
|
||||||
|
assert merged[:pleroma][:first_setting] == [key: "value", key2: [Pleroma.Repo]]
|
||||||
|
assert merged[:pleroma][:second_setting] == [key: "value2", key2: ["Activity"]]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "merged exported config" do
|
||||||
|
ExUnit.CaptureIO.capture_io(fn ->
|
||||||
|
merged =
|
||||||
|
ReleaseRuntimeProvider.load([],
|
||||||
|
exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert merged[:pleroma][:exported_config_merged]
|
||||||
|
end) =~
|
||||||
|
"!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "runtime config is merged with exported config" do
|
||||||
|
merged =
|
||||||
|
ReleaseRuntimeProvider.load([],
|
||||||
|
config_path: "test/fixtures/config/temp.secret.exs",
|
||||||
|
exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert merged[:pleroma][:first_setting] == [key2: [Pleroma.Repo], key: "new value"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,126 @@
|
|||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicyTest do
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF.FollowBotPolicy
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "FollowBotPolicy" do
|
||||||
|
test "follows remote users" do
|
||||||
|
bot = insert(:user, actor_type: "Service")
|
||||||
|
remote_user = insert(:user, local: false)
|
||||||
|
clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"to" => [remote_user.follower_address],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"content" => "Test post",
|
||||||
|
"type" => "Note",
|
||||||
|
"attributedTo" => remote_user.ap_id,
|
||||||
|
"inReplyTo" => nil
|
||||||
|
},
|
||||||
|
"actor" => remote_user.ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
refute User.following?(bot, remote_user)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(remote_user) |> length == 0
|
||||||
|
|
||||||
|
FollowBotPolicy.filter(message)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(remote_user) |> length == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not follow users with #nobot in bio" do
|
||||||
|
bot = insert(:user, actor_type: "Service")
|
||||||
|
remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"})
|
||||||
|
clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"to" => [remote_user.follower_address],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"content" => "I don't like follow bots",
|
||||||
|
"type" => "Note",
|
||||||
|
"attributedTo" => remote_user.ap_id,
|
||||||
|
"inReplyTo" => nil
|
||||||
|
},
|
||||||
|
"actor" => remote_user.ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
refute User.following?(bot, remote_user)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(remote_user) |> length == 0
|
||||||
|
|
||||||
|
FollowBotPolicy.filter(message)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(remote_user) |> length == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not follow local users" do
|
||||||
|
bot = insert(:user, actor_type: "Service")
|
||||||
|
local_user = insert(:user, local: true)
|
||||||
|
clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"to" => [local_user.follower_address],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"content" => "Hi I'm a local user",
|
||||||
|
"type" => "Note",
|
||||||
|
"attributedTo" => local_user.ap_id,
|
||||||
|
"inReplyTo" => nil
|
||||||
|
},
|
||||||
|
"actor" => local_user.ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
refute User.following?(bot, local_user)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(local_user) |> length == 0
|
||||||
|
|
||||||
|
FollowBotPolicy.filter(message)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(local_user) |> length == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not follow users requiring follower approval" do
|
||||||
|
bot = insert(:user, actor_type: "Service")
|
||||||
|
remote_user = insert(:user, %{local: false, is_locked: true})
|
||||||
|
clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
"to" => [remote_user.follower_address],
|
||||||
|
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
|
"type" => "Create",
|
||||||
|
"object" => %{
|
||||||
|
"content" => "I don't like randos following me",
|
||||||
|
"type" => "Note",
|
||||||
|
"attributedTo" => remote_user.ap_id,
|
||||||
|
"inReplyTo" => nil
|
||||||
|
},
|
||||||
|
"actor" => remote_user.ap_id
|
||||||
|
}
|
||||||
|
|
||||||
|
refute User.following?(bot, remote_user)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(remote_user) |> length == 0
|
||||||
|
|
||||||
|
FollowBotPolicy.filter(message)
|
||||||
|
|
||||||
|
assert User.get_follow_requests(remote_user) |> length == 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,172 @@
|
|||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.AddRemoveHandlingTest do
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
use Pleroma.DataCase, async: true
|
||||||
|
|
||||||
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
|
test "it accepts Add/Remove activities" do
|
||||||
|
user =
|
||||||
|
"test/fixtures/users_mock/user.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> String.replace("{{nickname}}", "lain")
|
||||||
|
|
||||||
|
object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
|
||||||
|
|
||||||
|
object =
|
||||||
|
"test/fixtures/statuses/note.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> String.replace("{{nickname}}", "lain")
|
||||||
|
|> String.replace("{{object_id}}", object_id)
|
||||||
|
|
||||||
|
object_url = "https://example.com/objects/#{object_id}"
|
||||||
|
|
||||||
|
actor = "https://example.com/users/lain"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^actor
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: user,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^object_url
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: object,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body:
|
||||||
|
"test/fixtures/users_mock/masto_featured.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> String.replace("{{domain}}", "example.com")
|
||||||
|
|> String.replace("{{nickname}}", "lain"),
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => object_url,
|
||||||
|
"target" => "https://example.com/users/lain/collections/featured",
|
||||||
|
"type" => "Add",
|
||||||
|
"to" => [Pleroma.Constants.as_public()],
|
||||||
|
"cc" => ["https://example.com/users/lain/followers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
|
||||||
|
assert activity.data == message
|
||||||
|
user = User.get_cached_by_ap_id(actor)
|
||||||
|
assert user.pinned_objects[object_url]
|
||||||
|
|
||||||
|
remove = %{
|
||||||
|
"id" => "http://localhost:400/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => object_url,
|
||||||
|
"target" => "https://example.com/users/lain/collections/featured",
|
||||||
|
"type" => "Remove",
|
||||||
|
"to" => [Pleroma.Constants.as_public()],
|
||||||
|
"cc" => ["https://example.com/users/lain/followers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(remove)
|
||||||
|
assert activity.data == remove
|
||||||
|
|
||||||
|
user = refresh_record(user)
|
||||||
|
refute user.pinned_objects[object_url]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Add/Remove activities for remote users without featured address" do
|
||||||
|
user = insert(:user, local: false, domain: "example.com")
|
||||||
|
|
||||||
|
user =
|
||||||
|
user
|
||||||
|
|> Ecto.Changeset.change(featured_address: nil)
|
||||||
|
|> Repo.update!()
|
||||||
|
|
||||||
|
%{host: host} = URI.parse(user.ap_id)
|
||||||
|
|
||||||
|
user_data =
|
||||||
|
"test/fixtures/users_mock/user.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> String.replace("{{nickname}}", user.nickname)
|
||||||
|
|
||||||
|
object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
|
||||||
|
|
||||||
|
object =
|
||||||
|
"test/fixtures/statuses/note.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> String.replace("{{nickname}}", user.nickname)
|
||||||
|
|> String.replace("{{object_id}}", object_id)
|
||||||
|
|
||||||
|
object_url = "https://#{host}/objects/#{object_id}"
|
||||||
|
|
||||||
|
actor = "https://#{host}/users/#{user.nickname}"
|
||||||
|
|
||||||
|
featured = "https://#{host}/users/#{user.nickname}/collections/featured"
|
||||||
|
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^actor
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: user_data,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^object_url
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: object,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{method: :get, url: ^featured} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body:
|
||||||
|
"test/fixtures/users_mock/masto_featured.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> String.replace("{{domain}}", "#{host}")
|
||||||
|
|> String.replace("{{nickname}}", user.nickname),
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
message = %{
|
||||||
|
"id" => "https://#{host}/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
|
||||||
|
"actor" => actor,
|
||||||
|
"object" => object_url,
|
||||||
|
"target" => "https://#{host}/users/#{user.nickname}/collections/featured",
|
||||||
|
"type" => "Add",
|
||||||
|
"to" => [Pleroma.Constants.as_public()],
|
||||||
|
"cc" => ["https://#{host}/users/#{user.nickname}/followers"]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, activity} = Transmogrifier.handle_incoming(message)
|
||||||
|
assert activity.data == message
|
||||||
|
user = User.get_cached_by_ap_id(actor)
|
||||||
|
assert user.pinned_objects[object_url]
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in new issue