Merge branch 'group-posts-fixes' into 'master'
Group posts fixes See merge request framasoft/mobilizon!618
This commit is contained in:
commit
c66e4d5d83
@ -97,7 +97,7 @@ import { FETCH_POST, CREATE_POST, UPDATE_POST, DELETE_POST } from "../../graphql
|
||||
|
||||
import { IPost, PostVisibility } from "../../types/post.model";
|
||||
import Editor from "../../components/Editor.vue";
|
||||
import { IActor, IGroup } from "../../types/actor";
|
||||
import { IActor, IGroup, usernameWithDomain } from "../../types/actor";
|
||||
import TagInput from "../../components/Event/TagInput.vue";
|
||||
import RouteName from "../../router/name";
|
||||
import Subtitle from "../../components/Utils/Subtitle.vue";
|
||||
@ -233,7 +233,7 @@ export default class EditPost extends Vue {
|
||||
if (data && this.post.attributedTo) {
|
||||
this.$router.push({
|
||||
name: RouteName.POSTS,
|
||||
params: { preferredUsername: this.post.attributedTo.preferredUsername },
|
||||
params: { preferredUsername: usernameWithDomain(this.post.attributedTo) },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,16 @@ const POSTS_PAGE_LIMIT = 10;
|
||||
components: {
|
||||
PostElementItem,
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
// if no subcomponents specify a metaInfo.title, this title will be used
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
title: this.$t("My groups") as string,
|
||||
// all titles will be injected into this template
|
||||
titleTemplate: "%s | Mobilizon",
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class PostList extends Vue {
|
||||
@Prop({ required: true, type: String }) preferredUsername!: string;
|
||||
|
@ -114,6 +114,7 @@
|
||||
:resource="localResource"
|
||||
:group="resource.actor"
|
||||
@delete="deleteResource"
|
||||
@rename="handleRename"
|
||||
@move="handleMove"
|
||||
v-else
|
||||
/>
|
||||
@ -143,7 +144,7 @@
|
||||
<section class="modal-card-body">
|
||||
<resource-selector
|
||||
:initialResource="updatedResource"
|
||||
:username="resource.actor.preferredUsername"
|
||||
:username="usernameWithDomain(resource.actor)"
|
||||
@updateResource="moveResource"
|
||||
@closeMoveModal="moveModal = false"
|
||||
/>
|
||||
@ -200,6 +201,7 @@ import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
|
||||
import ResourceItem from "@/components/Resource/ResourceItem.vue";
|
||||
import FolderItem from "@/components/Resource/FolderItem.vue";
|
||||
import Draggable from "vuedraggable";
|
||||
import { RefetchQueryDescription } from "apollo-client/core/watchQueryOptions";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../../graphql/actor";
|
||||
import { IActor, usernameWithDomain } from "../../types/actor";
|
||||
import RouteName from "../../router/name";
|
||||
@ -232,6 +234,9 @@ import ResourceSelector from "../../components/Resource/ResourceSelector.vue";
|
||||
username: this.$route.params.preferredUsername,
|
||||
};
|
||||
},
|
||||
error({ graphQLErrors }) {
|
||||
this.handleErrors(graphQLErrors);
|
||||
},
|
||||
},
|
||||
config: CONFIG,
|
||||
currentActor: CURRENT_ACTOR_CLIENT,
|
||||
@ -291,6 +296,13 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
|
||||
mapServiceTypeToIcon = mapServiceTypeToIcon;
|
||||
|
||||
get actualPath(): string {
|
||||
const path = Array.isArray(this.$route.params.path)
|
||||
? this.$route.params.path.join("/")
|
||||
: this.$route.params.path || this.path;
|
||||
return path[0] !== "/" ? `/${path}` : path;
|
||||
}
|
||||
|
||||
async createResource(): Promise<void> {
|
||||
if (!this.resource.actor) return;
|
||||
try {
|
||||
@ -305,34 +317,7 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
this.resource.id && this.resource.id.startsWith("root_") ? null : this.resource.id,
|
||||
type: this.newResource.type,
|
||||
},
|
||||
update: (store, { data: { createResource } }) => {
|
||||
if (createResource == null) return;
|
||||
if (!this.resource.actor) return;
|
||||
const cachedData = store.readQuery<{ resource: IResource }>({
|
||||
query: GET_RESOURCE,
|
||||
variables: {
|
||||
path: this.resource.path,
|
||||
username: this.resource.actor.preferredUsername,
|
||||
},
|
||||
});
|
||||
if (cachedData == null) return;
|
||||
const { resource } = cachedData;
|
||||
if (resource == null) {
|
||||
console.error("Cannot update resource cache, because of null value.");
|
||||
return;
|
||||
}
|
||||
const newResource: IResource = createResource;
|
||||
resource.children.elements = resource.children.elements.concat([newResource]);
|
||||
|
||||
store.writeQuery({
|
||||
query: GET_RESOURCE,
|
||||
variables: {
|
||||
path: this.resource.path,
|
||||
username: this.resource.actor.preferredUsername,
|
||||
},
|
||||
data: { resource },
|
||||
});
|
||||
},
|
||||
refetchQueries: () => this.postRefreshQueries(),
|
||||
});
|
||||
this.createLinkResourceModal = false;
|
||||
this.createResourceModal = false;
|
||||
@ -429,6 +414,19 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
private postRefreshQueries(): RefetchQueryDescription {
|
||||
return [
|
||||
{
|
||||
query: GET_RESOURCE,
|
||||
variables: {
|
||||
path: this.actualPath,
|
||||
username: this.$route.params.preferredUsername,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async deleteResource(resourceID: string): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
@ -436,37 +434,7 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
variables: {
|
||||
id: resourceID,
|
||||
},
|
||||
update: (store, { data: { deleteResource } }) => {
|
||||
if (deleteResource == null) return;
|
||||
if (!this.resource.actor) return;
|
||||
const cachedData = store.readQuery<{ resource: IResource }>({
|
||||
query: GET_RESOURCE,
|
||||
variables: {
|
||||
path: this.resource.path,
|
||||
username: this.resource.actor.preferredUsername,
|
||||
},
|
||||
});
|
||||
if (cachedData == null) return;
|
||||
const { resource } = cachedData;
|
||||
if (resource == null) {
|
||||
console.error("Cannot update resource cache, because of null value.");
|
||||
return;
|
||||
}
|
||||
const oldResource: IResource = deleteResource;
|
||||
|
||||
resource.children.elements = resource.children.elements.filter(
|
||||
(resourceElement) => resourceElement.id !== oldResource.id
|
||||
);
|
||||
|
||||
store.writeQuery({
|
||||
query: GET_RESOURCE,
|
||||
variables: {
|
||||
path: this.resource.path,
|
||||
username: this.resource.actor.preferredUsername,
|
||||
},
|
||||
data: { resource },
|
||||
});
|
||||
},
|
||||
refetchQueries: () => this.postRefreshQueries(),
|
||||
});
|
||||
this.validCheckedResources = this.validCheckedResources.filter((id) => id !== resourceID);
|
||||
delete this.checkedResources[resourceID];
|
||||
@ -476,6 +444,7 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
}
|
||||
|
||||
handleRename(resource: IResource): void {
|
||||
console.log("handleRename");
|
||||
this.renameModal = true;
|
||||
this.updatedResource = { ...resource };
|
||||
}
|
||||
@ -506,6 +475,7 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
parentId: resource.parent ? resource.parent.id : null,
|
||||
path: resource.path,
|
||||
},
|
||||
refetchQueries: () => this.postRefreshQueries(),
|
||||
update: (store, { data }) => {
|
||||
if (!data || data.updateResource == null || parentPath == null) return;
|
||||
if (!this.resource.actor) return;
|
||||
@ -577,9 +547,19 @@ export default class Resources extends Mixins(ResourceMixin) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
handleErrors(errors: any[]): void {
|
||||
if (errors.some((error) => error.status_code === 404)) {
|
||||
this.$router.replace({ name: RouteName.PAGE_NOT_FOUND });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.container.section {
|
||||
background: $white;
|
||||
}
|
||||
|
||||
nav.breadcrumb ul {
|
||||
align-items: center;
|
||||
|
||||
|
@ -15,6 +15,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
Config,
|
||||
Discussions,
|
||||
Events,
|
||||
Posts,
|
||||
Resources,
|
||||
Share,
|
||||
Users
|
||||
@ -88,6 +89,7 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
{:existing, Discussions.get_discussion_by_url(url)},
|
||||
{:existing, nil} <- {:existing, Discussions.get_comment_from_url(url)},
|
||||
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
|
||||
{:existing, nil} <- {:existing, Posts.get_post_by_url(url)},
|
||||
{:existing, nil} <-
|
||||
{:existing, Actors.get_actor_by_url_2(url)},
|
||||
{:existing, nil} <- {:existing, Actors.get_member_by_url(url)},
|
||||
@ -109,6 +111,9 @@ defmodule Mobilizon.Federation.ActivityPub do
|
||||
|
||||
{:error, "Gone"} ->
|
||||
{:error, "Gone", entity}
|
||||
|
||||
{:error, "Not found"} ->
|
||||
{:error, "Not found", entity}
|
||||
end
|
||||
else
|
||||
{:ok, entity}
|
||||
|
@ -50,7 +50,7 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do
|
||||
|
||||
def handle(:incoming_ap_doc, params) do
|
||||
Logger.info("Handling incoming AP activity")
|
||||
Logger.debug(inspect(params))
|
||||
Logger.debug(inspect(Map.drop(params, ["@context"])))
|
||||
|
||||
case Transmogrifier.handle_incoming(params) do
|
||||
{:ok, activity, _data} ->
|
||||
|
@ -32,6 +32,10 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
|
||||
Logger.warn("Resource at #{url} is 410 Gone")
|
||||
{:error, "Gone"}
|
||||
|
||||
{:ok, %Tesla.Env{status: 404}} ->
|
||||
Logger.warn("Resource at #{url} is 404 Gone")
|
||||
{:error, "Not found"}
|
||||
|
||||
{:ok, %Tesla.Env{} = res} ->
|
||||
{:error, res}
|
||||
end
|
||||
|
@ -4,10 +4,11 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
|
||||
"""
|
||||
|
||||
# TODO: Move me in a more appropriate place
|
||||
alias Mobilizon.{Actors, Discussions, Events, Resources}
|
||||
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Tombstone
|
||||
|
||||
@ -23,6 +24,9 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
|
||||
def maybe_preload(%Resource{url: url}),
|
||||
do: {:ok, Resources.get_resource_by_url_with_preloads(url)}
|
||||
|
||||
def maybe_preload(%Post{url: url}),
|
||||
do: {:ok, Posts.get_post_by_url_with_preloads(url)}
|
||||
|
||||
def maybe_preload(%Actor{url: url}), do: {:ok, Actors.get_actor_by_url!(url, true)}
|
||||
|
||||
def maybe_preload(%Member{} = member), do: {:ok, member}
|
||||
|
@ -118,7 +118,9 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
|
||||
|
||||
defp process_collection(_, _), do: :error
|
||||
|
||||
defp handling_element(data) when is_map(data) do
|
||||
# If we're handling an activity
|
||||
defp handling_element(%{"type" => activity_type} = data)
|
||||
when activity_type in ["Create", "Update", "Delete"] do
|
||||
object = get_in(data, ["object"])
|
||||
|
||||
if object do
|
||||
@ -128,6 +130,26 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
|
||||
Transmogrifier.handle_incoming(data)
|
||||
end
|
||||
|
||||
# If we're handling directly an object
|
||||
defp handling_element(data) when is_map(data) do
|
||||
object = get_in(data, ["object"])
|
||||
|
||||
if object do
|
||||
object |> Utils.get_url() |> Mobilizon.Tombstone.delete_uri_tombstone()
|
||||
end
|
||||
|
||||
activity = %{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
"actor" => data["actor"] || data["attributedTo"],
|
||||
"attributedTo" => data["attributedTo"] || data["actor"],
|
||||
"object" => data
|
||||
}
|
||||
|
||||
Transmogrifier.handle_incoming(activity)
|
||||
end
|
||||
|
||||
defp handling_element(uri) when is_binary(uri) do
|
||||
ActivityPub.fetch_object_from_url(uri)
|
||||
end
|
||||
|
@ -381,12 +381,15 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
update_data
|
||||
) do
|
||||
with actor <- Utils.get_actor(update_data),
|
||||
{:ok, %Actor{url: actor_url, suspended: false}} <-
|
||||
{:ok, %Actor{url: actor_url, suspended: false} = actor} <-
|
||||
ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||
{:ok, %Event{} = old_event} <-
|
||||
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
|
||||
object_data <- Converter.Event.as_to_model_data(object),
|
||||
{:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)},
|
||||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, update_data) ||
|
||||
Utils.can_update_group_object?(actor, old_event)},
|
||||
{:ok, %Activity{} = activity, %Event{} = new_event} <-
|
||||
ActivityPub.update(old_event, object_data, false) do
|
||||
{:ok, activity, new_event}
|
||||
@ -418,6 +421,57 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Update", "object" => %{"type" => "Article"} = object, "actor" => _actor} =
|
||||
update_data
|
||||
) do
|
||||
with actor <- Utils.get_actor(update_data),
|
||||
{:ok, %Actor{url: actor_url, suspended: false} = actor} <-
|
||||
ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||
{:ok, %Post{} = old_post} <-
|
||||
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
|
||||
object_data <- Converter.Post.as_to_model_data(object),
|
||||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, update_data["object"]) ||
|
||||
Utils.can_update_group_object?(actor, old_post)},
|
||||
{:ok, %Activity{} = activity, %Post{} = new_post} <-
|
||||
ActivityPub.update(old_post, object_data, false) do
|
||||
{:ok, activity, new_post}
|
||||
else
|
||||
{:origin_check, _} ->
|
||||
Logger.warn("Actor tried to update a post but doesn't has the required role")
|
||||
:error
|
||||
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Update", "object" => %{"type" => type} = object, "actor" => _actor} =
|
||||
update_data
|
||||
)
|
||||
when type in ["ResourceCollection", "Document"] do
|
||||
with actor <- Utils.get_actor(update_data),
|
||||
{:ok, %Actor{url: actor_url, suspended: false}} <-
|
||||
ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||
{:ok, %Resource{} = old_resource} <-
|
||||
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
|
||||
object_data <- Converter.Resource.as_to_model_data(object),
|
||||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, update_data) ||
|
||||
Utils.can_update_group_object?(actor, old_resource)},
|
||||
{:ok, %Activity{} = activity, %Resource{} = new_resource} <-
|
||||
ActivityPub.update(old_resource, object_data, false) do
|
||||
{:ok, activity, new_resource}
|
||||
else
|
||||
_e ->
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Update", "object" => %{"type" => "Member"} = object, "actor" => _actor} =
|
||||
update_data
|
||||
@ -505,11 +559,11 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
with actor_url <- Utils.get_actor(data),
|
||||
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url),
|
||||
object_id <- Utils.get_url(object),
|
||||
{:error, "Gone", object} <- ActivityPub.fetch_object_from_url(object_id, force: true),
|
||||
{:ok, object} <- is_group_object_gone(object_id),
|
||||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check_from_id?(actor_url, object_id) ||
|
||||
Utils.activity_actor_is_group_member?(actor, object)},
|
||||
Utils.can_delete_group_object?(actor, object)},
|
||||
{:ok, activity, object} <- ActivityPub.delete(object, actor, false) do
|
||||
{:ok, activity, object}
|
||||
else
|
||||
@ -523,6 +577,29 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Move", "object" => %{"type" => type} = object, "actor" => _actor} = data
|
||||
)
|
||||
when type in ["ResourceCollection", "Document"] do
|
||||
with actor <- Utils.get_actor(data),
|
||||
{:ok, %Actor{url: actor_url, suspended: false} = actor} <-
|
||||
ActivityPub.get_or_fetch_actor_by_url(actor),
|
||||
{:ok, %Resource{} = old_resource} <-
|
||||
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
|
||||
object_data <- Converter.Resource.as_to_model_data(object),
|
||||
{:origin_check, true} <-
|
||||
{:origin_check,
|
||||
Utils.origin_check?(actor_url, data) ||
|
||||
Utils.can_update_group_object?(actor, old_resource)},
|
||||
{:ok, activity, new_resource} <- ActivityPub.move(:resource, old_resource, object_data) do
|
||||
{:ok, activity, new_resource}
|
||||
else
|
||||
e ->
|
||||
Logger.error(inspect(e))
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{
|
||||
"type" => "Join",
|
||||
@ -975,4 +1052,25 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
|
||||
fetch_object_optionnally_authenticated(url, actor)
|
||||
end
|
||||
end
|
||||
|
||||
defp is_group_object_gone(object_id) do
|
||||
case ActivityPub.fetch_object_from_url(object_id, force: true) do
|
||||
{:error, error_message, object} when error_message in ["Gone", "Not found"] ->
|
||||
{:ok, object}
|
||||
|
||||
{:ok, %{url: url} = object} ->
|
||||
if Utils.are_same_origin?(url, Endpoint.url()),
|
||||
do: {:ok, object},
|
||||
else: {:error, "Group object URL remote"}
|
||||
|
||||
{:error, {:error, err}} ->
|
||||
{:error, err}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
|
||||
err ->
|
||||
err
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -88,6 +88,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Actors do
|
||||
|
||||
def group_actor(%Actor{} = actor), do: actor
|
||||
|
||||
def role_needed_to_update(%Actor{} = _group), do: :administrator
|
||||
def role_needed_to_delete(%Actor{} = _group), do: :administrator
|
||||
|
||||
defp prepare_args_for_actor(args) do
|
||||
args
|
||||
|> maybe_sanitize_username()
|
||||
|
@ -98,6 +98,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Comments do
|
||||
|
||||
def group_actor(_), do: nil
|
||||
|
||||
def role_needed_to_update(%Comment{attributed_to: %Actor{} = _group}), do: :administrator
|
||||
def role_needed_to_delete(%Comment{attributed_to_id: _attributed_to_id}), do: :administrator
|
||||
|
||||
# Prepare and sanitize arguments for comments
|
||||
defp prepare_args_for_comment(args) do
|
||||
with in_reply_to_comment <-
|
||||
|
@ -89,6 +89,9 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
|
||||
|
||||
def group_actor(%Discussion{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
def role_needed_to_update(%Discussion{}), do: :moderator
|
||||
def role_needed_to_delete(%Discussion{}), do: :moderator
|
||||
|
||||
@spec maybe_publish_graphql_subscription(Discussion.t()) :: :ok
|
||||
defp maybe_publish_graphql_subscription(%Discussion{} = discussion) do
|
||||
Absinthe.Subscription.publish(Endpoint, discussion,
|
||||
|
@ -57,6 +57,8 @@ defprotocol Mobilizon.Federation.ActivityPub.Types.Managable do
|
||||
end
|
||||
|
||||
defprotocol Mobilizon.Federation.ActivityPub.Types.Ownable do
|
||||
@type group_role :: :member | :moderator | :administrator | nil
|
||||
|
||||
@spec group_actor(Entity.t()) :: Actor.t() | nil
|
||||
@doc "Returns an eventual group for the entity"
|
||||
def group_actor(entity)
|
||||
@ -64,6 +66,12 @@ defprotocol Mobilizon.Federation.ActivityPub.Types.Ownable do
|
||||
@spec actor(Entity.t()) :: Actor.t() | nil
|
||||
@doc "Returns the actor for the entity"
|
||||
def actor(entity)
|
||||
|
||||
@spec role_needed_to_update(Entity.t()) :: group_role()
|
||||
def role_needed_to_update(entity)
|
||||
|
||||
@spec role_needed_to_delete(Entity.t()) :: group_role()
|
||||
def role_needed_to_delete(entity)
|
||||
end
|
||||
|
||||
defimpl Managable, for: Event do
|
||||
@ -74,6 +82,8 @@ end
|
||||
defimpl Ownable, for: Event do
|
||||
defdelegate group_actor(entity), to: Events
|
||||
defdelegate actor(entity), to: Events
|
||||
defdelegate role_needed_to_update(entity), to: Events
|
||||
defdelegate role_needed_to_delete(entity), to: Events
|
||||
end
|
||||
|
||||
defimpl Managable, for: Comment do
|
||||
@ -84,6 +94,8 @@ end
|
||||
defimpl Ownable, for: Comment do
|
||||
defdelegate group_actor(entity), to: Comments
|
||||
defdelegate actor(entity), to: Comments
|
||||
defdelegate role_needed_to_update(entity), to: Comments
|
||||
defdelegate role_needed_to_delete(entity), to: Comments
|
||||
end
|
||||
|
||||
defimpl Managable, for: Post do
|
||||
@ -94,6 +106,8 @@ end
|
||||
defimpl Ownable, for: Post do
|
||||
defdelegate group_actor(entity), to: Posts
|
||||
defdelegate actor(entity), to: Posts
|
||||
defdelegate role_needed_to_update(entity), to: Posts
|
||||
defdelegate role_needed_to_delete(entity), to: Posts
|
||||
end
|
||||
|
||||
defimpl Managable, for: Actor do
|
||||
@ -104,6 +118,8 @@ end
|
||||
defimpl Ownable, for: Actor do
|
||||
defdelegate group_actor(entity), to: Actors
|
||||
defdelegate actor(entity), to: Actors
|
||||
defdelegate role_needed_to_update(entity), to: Actors
|
||||
defdelegate role_needed_to_delete(entity), to: Actors
|
||||
end
|
||||
|
||||
defimpl Managable, for: TodoList do
|
||||
@ -114,6 +130,8 @@ end
|
||||
defimpl Ownable, for: TodoList do
|
||||
defdelegate group_actor(entity), to: TodoLists
|
||||
defdelegate actor(entity), to: TodoLists
|
||||
defdelegate role_needed_to_update(entity), to: TodoLists
|
||||
defdelegate role_needed_to_delete(entity), to: TodoLists
|
||||
end
|
||||
|
||||
defimpl Managable, for: Todo do
|
||||
@ -124,6 +142,8 @@ end
|
||||
defimpl Ownable, for: Todo do
|
||||
defdelegate group_actor(entity), to: Todos
|
||||
defdelegate actor(entity), to: Todos
|
||||
defdelegate role_needed_to_update(entity), to: Todos
|
||||
defdelegate role_needed_to_delete(entity), to: Todos
|
||||
end
|
||||
|
||||
defimpl Managable, for: Resource do
|
||||
@ -134,6 +154,8 @@ end
|
||||
defimpl Ownable, for: Resource do
|
||||
defdelegate group_actor(entity), to: Resources
|
||||
defdelegate actor(entity), to: Resources
|
||||
defdelegate role_needed_to_update(entity), to: Resources
|
||||
defdelegate role_needed_to_delete(entity), to: Resources
|
||||
end
|
||||
|
||||
defimpl Managable, for: Discussion do
|
||||
@ -144,11 +166,15 @@ end
|
||||
defimpl Ownable, for: Discussion do
|
||||
defdelegate group_actor(entity), to: Discussions
|
||||
defdelegate actor(entity), to: Discussions
|
||||
defdelegate role_needed_to_update(entity), to: Discussions
|
||||
defdelegate role_needed_to_delete(entity), to: Discussions
|
||||
end
|
||||
|
||||
defimpl Ownable, for: Tombstone do
|
||||
defdelegate group_actor(entity), to: Tombstones
|
||||
defdelegate actor(entity), to: Tombstones
|
||||
defdelegate role_needed_to_update(entity), to: Tombstones
|
||||
defdelegate role_needed_to_delete(entity), to: Tombstones
|
||||
end
|
||||
|
||||
defimpl Managable, for: Member do
|
||||
|
@ -88,6 +88,10 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Events do
|
||||
|
||||
def group_actor(_), do: nil
|
||||
|
||||
def role_needed_to_update(%Event{attributed_to: %Actor{} = _group}), do: :moderator
|
||||
def role_needed_to_delete(%Event{attributed_to_id: _attributed_to_id}), do: :moderator
|
||||
def role_needed_to_delete(_), do: nil
|
||||
|
||||
def join(%Event{} = event, %Actor{} = actor, _local, additional) do
|
||||
with {:maximum_attendee_capacity, true} <-
|
||||
{:maximum_attendee_capacity, check_attendee_capacity(event)},
|
||||
|
@ -1,6 +1,6 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
@moduledoc false
|
||||
alias Mobilizon.{Actors, Posts}
|
||||
alias Mobilizon.{Actors, Posts, Tombstone}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub.Types.Entity
|
||||
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
|
||||
@ -11,6 +11,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
|
||||
@behaviour Entity
|
||||
|
||||
@public_ap "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
@impl Entity
|
||||
def create(args, additional) do
|
||||
with args <- Map.update(args, :tags, [], &ConverterUtils.fetch_tags/1),
|
||||
@ -42,7 +44,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
{:ok, %Post{attributed_to_id: group_id, author_id: creator_id} = post} <-
|
||||
Posts.update_post(post, args),
|
||||
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
|
||||
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||
{:ok, %Actor{url: group_url} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||
%Actor{url: creator_url} = creator <- Actors.get_actor(creator_id),
|
||||
post_as_data <-
|
||||
Convertible.model_to_as(%{post | attributed_to: group, author: creator}),
|
||||
@ -50,7 +52,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
"to" => [group.members_url],
|
||||
"cc" => [],
|
||||
"actor" => creator_url,
|
||||
"attributedTo" => [creator_url]
|
||||
"attributedTo" => [group_url]
|
||||
} do
|
||||
update_data = make_update_data(post_as_data, Map.merge(audience, additional))
|
||||
|
||||
@ -66,7 +68,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
def delete(
|
||||
%Post{
|
||||
url: url,
|
||||
attributed_to: %Actor{url: group_url}
|
||||
attributed_to: %Actor{url: group_url, members_url: members_url}
|
||||
} = post,
|
||||
%Actor{url: actor_url} = actor,
|
||||
_local,
|
||||
@ -77,11 +79,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
"type" => "Delete",
|
||||
"object" => Convertible.model_to_as(post),
|
||||
"id" => url <> "/delete",
|
||||
"to" => [group_url]
|
||||
"to" => [group_url, @public_ap, members_url]
|
||||
}
|
||||
|
||||
with {:ok, _post} <- Posts.delete_post(post),
|
||||
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}") do
|
||||
with {:ok, %Post{} = post} <- Posts.delete_post(post),
|
||||
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
|
||||
{:ok, %Tombstone{} = _tombstone} <-
|
||||
Tombstone.create_tombstone(%{uri: post.url, actor_id: actor.id}) do
|
||||
{:ok, activity_data, actor, post}
|
||||
end
|
||||
end
|
||||
@ -91,4 +95,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
|
||||
|
||||
def group_actor(%Post{attributed_to_id: attributed_to_id}),
|
||||
do: Actors.get_actor(attributed_to_id)
|
||||
|
||||
def role_needed_to_update(%Post{}), do: :moderator
|
||||
def role_needed_to_delete(%Post{}), do: :moderator
|
||||
end
|
||||
|
@ -155,4 +155,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Resources do
|
||||
do: Actors.get_actor(creator_id)
|
||||
|
||||
def group_actor(%Resource{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
def role_needed_to_update(%Resource{}), do: :member
|
||||
def role_needed_to_delete(%Resource{}), do: :member
|
||||
end
|
||||
|
@ -67,4 +67,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.TodoLists do
|
||||
def actor(%TodoList{}), do: nil
|
||||
|
||||
def group_actor(%TodoList{actor_id: actor_id}), do: Actors.get_actor(actor_id)
|
||||
|
||||
def role_needed_to_update(%TodoList{}), do: :member
|
||||
def role_needed_to_delete(%TodoList{}), do: :member
|
||||
end
|
||||
|
@ -79,4 +79,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Todos do
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def role_needed_to_update(%Todo{}), do: :member
|
||||
def role_needed_to_delete(%Todo{}), do: :member
|
||||
end
|
||||
|
@ -11,4 +11,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Tombstones do
|
||||
def actor(_), do: nil
|
||||
|
||||
def group_actor(_), do: nil
|
||||
|
||||
def role_needed_to_update(%Actor{}), do: nil
|
||||
def role_needed_to_delete(%Actor{}), do: nil
|
||||
end
|
||||
|
@ -287,10 +287,41 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
||||
def origin_check_from_id?(id, %{"id" => other_id} = _params) when is_binary(other_id),
|
||||
do: origin_check_from_id?(id, other_id)
|
||||
|
||||
def activity_actor_is_group_member?(%Actor{id: actor_id}, object) do
|
||||
def activity_actor_is_group_member?(
|
||||
%Actor{id: actor_id, url: actor_url},
|
||||
object,
|
||||
role \\ :member
|
||||
) do
|
||||
case Ownable.group_actor(object) do
|
||||
%Actor{type: :Group, id: group_id} ->
|
||||
%Actor{type: :Group, id: group_id, url: group_url} ->
|
||||
Logger.debug("Group object url is #{group_url}")
|
||||
|
||||
case role do
|
||||
:moderator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a moderator from group from #{
|
||||
object.url
|
||||
}"
|
||||
)
|
||||
|
||||
Actors.is_moderator?(actor_id, group_id)
|
||||
|
||||
:administrator ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is an administrator from group from #{
|
||||
object.url
|
||||
}"
|
||||
)
|
||||
|
||||
Actors.is_administrator?(actor_id, group_id)
|
||||
|
||||
_ ->
|
||||
Logger.debug(
|
||||
"Checking if activity actor #{actor_url} is a member from group from #{object.url}"
|
||||
)
|
||||
|
||||
Actors.is_member?(actor_id, group_id)
|
||||
end
|
||||
|
||||
_ ->
|
||||
false
|
||||
@ -628,4 +659,39 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def can_update_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:role_needed_to_update, actor, object)
|
||||
end
|
||||
|
||||
def can_delete_group_object?(%Actor{} = actor, object) do
|
||||
can_manage_group_object?(:role_needed_to_delete, actor, object)
|
||||
end
|
||||
|
||||
@spec can_manage_group_object?(
|
||||
:role_needed_to_update | :role_needed_to_delete,
|
||||
Actor.t(),
|
||||
any()
|
||||
) :: boolean()
|
||||
defp can_manage_group_object?(action_function, %Actor{url: actor_url} = actor, object) do
|
||||
if Ownable.group_actor(object) != nil do
|
||||
case apply(Ownable, action_function, [object]) do
|
||||
role when role in [:member, :moderator, :administrator] ->
|
||||
activity_actor_is_group_member?(actor, object, role)
|
||||
|
||||
_ ->
|
||||
case action_function do
|
||||
:role_needed_to_update ->
|
||||
Logger.warn("Actor #{actor_url} can't update #{object.url}")
|
||||
|
||||
:role_needed_to_delete ->
|
||||
Logger.warn("Actor #{actor_url} can't delete #{object.url}")
|
||||
end
|
||||
|
||||
false
|
||||
end
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -88,6 +88,7 @@ defmodule Mobilizon.GraphQL.Error do
|
||||
defp metadata(:post_not_found), do: {404, dgettext("errors", "Post not found")}
|
||||
defp metadata(:event_not_found), do: {404, dgettext("errors", "Event not found")}
|
||||
defp metadata(:group_not_found), do: {404, dgettext("errors", "Group not found")}
|
||||
defp metadata(:resource_not_found), do: {404, dgettext("errors", "Resource not found")}
|
||||
defp metadata(:unknown), do: {500, dgettext("errors", "Something went wrong")}
|
||||
|
||||
defp metadata(code) do
|
||||
|
@ -149,7 +149,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
||||
} = _resolution
|
||||
) do
|
||||
with {:uuid, {:ok, _uuid}} <- {:uuid, Ecto.UUID.cast(id)},
|
||||
%Actor{id: actor_id} <- Users.get_actor_for_user(user),
|
||||
%Actor{id: actor_id, url: actor_url} <- Users.get_actor_for_user(user),
|
||||
{:post, %Post{attributed_to: %Actor{id: group_id} = group} = post} <-
|
||||
{:post, Posts.get_post_with_preloads(id)},
|
||||
args <-
|
||||
@ -158,7 +158,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
||||
end),
|
||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
||||
{:ok, _, %Post{} = post} <-
|
||||
ActivityPub.update(post, args, true, %{}) do
|
||||
ActivityPub.update(post, args, true, %{"actor" => actor_url}) do
|
||||
{:ok, post}
|
||||
else
|
||||
{:uuid, :error} ->
|
||||
|
@ -83,8 +83,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
|
||||
{:resource, Resources.get_resource_by_group_and_path_with_preloads(group_id, path)} do
|
||||
{:ok, resource}
|
||||
else
|
||||
{:group, _} -> {:error, :group_not_found}
|
||||
{:member, false} -> {:error, dgettext("errors", "Profile is not member of group")}
|
||||
{:resource, _} -> {:error, dgettext("errors", "No such resource")}
|
||||
{:resource, _} -> {:error, :resource_not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@ -137,12 +138,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
|
||||
}
|
||||
} = _resolution
|
||||
) do
|
||||
with %Actor{id: actor_id} <- Users.get_actor_for_user(user),
|
||||
with %Actor{id: actor_id, url: actor_url} <- Users.get_actor_for_user(user),
|
||||
{:resource, %Resource{actor_id: group_id} = resource} <-
|
||||
{:resource, Resources.get_resource_with_preloads(resource_id)},
|
||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
||||
{:ok, _, %Resource{} = resource} <-
|
||||
ActivityPub.update(resource, args, true, %{}) do
|
||||
ActivityPub.update(resource, args, true, %{"actor" => actor_url}) do
|
||||
{:ok, resource}
|
||||
else
|
||||
{:resource, _} ->
|
||||
@ -195,8 +196,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
|
||||
}
|
||||
} = _resolution
|
||||
) do
|
||||
with {:ok, data} when is_map(data) <- Parser.parse(resource_url) do
|
||||
case Parser.parse(resource_url) do
|
||||
{:ok, data} when is_map(data) ->
|
||||
{:ok, struct(Metadata, data)}
|
||||
|
||||
{:error, _err} ->
|
||||
Logger.warn("Error while fetching preview from #{inspect(resource_url)}")
|
||||
{:error, :unknown_resource}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -406,7 +406,7 @@ defmodule Mobilizon.Events do
|
||||
def list_public_events_for_actor(actor, page \\ nil, limit \\ nil)
|
||||
|
||||
def list_public_events_for_actor(%Actor{type: :Group} = group, page, limit),
|
||||
do: list_organized_events_for_group(group, :public, nil, page, limit)
|
||||
do: list_organized_events_for_group(group, :public, nil, nil, page, limit)
|
||||
|
||||
def list_public_events_for_actor(%Actor{id: actor_id}, page, limit) do
|
||||
actor_id
|
||||
|
23
lib/service/http/rich_media_preview_client.ex
Normal file
23
lib/service/http/rich_media_preview_client.ex
Normal file
@ -0,0 +1,23 @@
|
||||
defmodule Mobilizon.Service.HTTP.RichMediaPreviewClient do
|
||||
@moduledoc """
|
||||
Tesla HTTP Basic Client
|
||||
with JSON middleware
|
||||
"""
|
||||
|
||||
use Tesla
|
||||
alias Mobilizon.Config
|
||||
|
||||
@default_opts [
|
||||
recv_timeout: 20_000
|
||||
]
|
||||
|
||||
adapter(Tesla.Adapter.Hackney, @default_opts)
|
||||
|
||||
@user_agent Config.instance_user_agent()
|
||||
|
||||
plug(Tesla.Middleware.FollowRedirects)
|
||||
|
||||
plug(Tesla.Middleware.Timeout, timeout: 10_000)
|
||||
|
||||
plug(Tesla.Middleware.Headers, [{"User-Agent", @user_agent}])
|
||||
end
|
@ -17,6 +17,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
|
||||
]
|
||||
|
||||
alias Mobilizon.Config
|
||||
alias Mobilizon.Service.HTTP.RichMediaPreviewClient
|
||||
alias Mobilizon.Service.RichMedia.Favicon
|
||||
alias Plug.Conn.Utils
|
||||
require Logger
|
||||
@ -56,7 +57,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
|
||||
with {:ok, _} <- prevent_local_address(url),
|
||||
{:ok, %{body: body, status: code, headers: response_headers}}
|
||||
when code in 200..299 <-
|
||||
Tesla.get(
|
||||
RichMediaPreviewClient.get(
|
||||
url,
|
||||
headers: headers,
|
||||
opts: @options
|
||||
@ -188,8 +189,11 @@ defmodule Mobilizon.Service.RichMedia.Parser do
|
||||
defp maybe_parse(html) do
|
||||
Enum.reduce_while(parsers(), %{}, fn parser, acc ->
|
||||
case parser.parse(html, acc) do
|
||||
{:ok, data} -> {:halt, data}
|
||||
{:error, _msg} -> {:cont, acc}
|
||||
{:ok, data} ->
|
||||
{:halt, data}
|
||||
|
||||
{:error, _msg} ->
|
||||
{:cont, acc}
|
||||
end
|
||||
end)
|
||||
end
|
||||
@ -237,7 +241,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
|
||||
!String.ends_with?(hostname, ".localhost")
|
||||
|
||||
defp validate_hostname_only(hostname),
|
||||
do: hostname |> String.graphemes() |> Enum.count(&(&1 == "o")) > 0
|
||||
do: hostname |> String.graphemes() |> Enum.count(&(&1 == ".")) > 0
|
||||
|
||||
defp validate_ip(hostname) do
|
||||
case hostname |> String.to_charlist() |> :inet.parse_address() do
|
||||
|
@ -21,7 +21,7 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OEmbed do
|
||||
with elements = [_ | _] <- get_discovery_data(html),
|
||||
{:ok, oembed_url} <- get_oembed_url(elements),
|
||||
{:ok, oembed_data} <- get_oembed_data(oembed_url),
|
||||
oembed_data <- filter_oembed_data(oembed_data) do
|
||||
{:ok, oembed_data} <- filter_oembed_data(oembed_data) do
|
||||
Logger.debug("Data found with OEmbed parser")
|
||||
Logger.debug(inspect(oembed_data))
|
||||
{:ok, oembed_data}
|
||||
@ -55,7 +55,9 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OEmbed do
|
||||
{:error, "No type declared for OEmbed data"}
|
||||
|
||||
"link" ->
|
||||
Map.put(data, :image_remote_url, Map.get(data, :thumbnail_url))
|
||||
data
|
||||
|> Map.put(:image_remote_url, Map.get(data, :thumbnail_url))
|
||||
|> (&{:ok, &1}).()
|
||||
|
||||
"photo" ->
|
||||
if Map.get(data, :url, "") == "" do
|
||||
@ -65,6 +67,7 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OEmbed do
|
||||
|> Map.put(:image_remote_url, Map.get(data, :url))
|
||||
|> Map.put(:width, Map.get(data, :width, 0))
|
||||
|> Map.put(:height, Map.get(data, :height, 0))
|
||||
|> (&{:ok, &1}).()
|
||||
end
|
||||
|
||||
"video" ->
|
||||
@ -75,6 +78,7 @@ defmodule Mobilizon.Service.RichMedia.Parsers.OEmbed do
|
||||
|> Map.put(:width, Map.get(data, :width, 0))
|
||||
|> Map.put(:height, Map.get(data, :height, 0))
|
||||
|> Map.put(:image_remote_url, Map.get(data, :thumbnail_url))
|
||||
|> (&{:ok, &1}).()
|
||||
|
||||
"rich" ->
|
||||
{:error, "OEmbed data has rich type, which we don't support"}
|
||||
|
317
test/federation/activity_pub/transmogrifier/delete_test.exs
Normal file
317
test/federation/activity_pub/transmogrifier/delete_test.exs
Normal file
@ -0,0 +1,317 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
|
||||
use Mobilizon.DataCase
|
||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||
use Oban.Testing, repo: Mobilizon.Storage.Repo
|
||||
import Mobilizon.Factory
|
||||
import ExUnit.CaptureLog
|
||||
import Mox
|
||||
|
||||
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Resources.Resource
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
alias Mobilizon.Service.HTTP.ActivityPub.Mock
|
||||
|
||||
describe "handle incoming delete activities" do
|
||||
test "it works for incoming deletes" do
|
||||
%Actor{url: actor_url} =
|
||||
actor = insert(:actor, url: "http://mobilizon.tld/@remote", domain: "mobilizon.tld")
|
||||
|
||||
%Comment{url: comment_url} =
|
||||
insert(:comment,
|
||||
actor: nil,
|
||||
actor_id: actor.id,
|
||||
url: "http://mobilizon.tld/comments/9f3794b8-11a0-4a98-8cb7-475ab332c701"
|
||||
)
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "http://mobilizon.tld/comments/9f3794b8-11a0-4a98-8cb7-475ab332c701"},
|
||||
_opts ->
|
||||
{:ok, %Tesla.Env{status: 410, body: "Gone"}}
|
||||
end)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("id", comment_url)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("actor", actor_url)
|
||||
|
||||
assert Discussions.get_comment_from_url(comment_url)
|
||||
assert is_nil(Discussions.get_comment_from_url(comment_url).deleted_at)
|
||||
|
||||
{:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
refute is_nil(Discussions.get_comment_from_url(comment_url).deleted_at)
|
||||
end
|
||||
|
||||
test "it fails for incoming deletes with spoofed origin" do
|
||||
comment = insert(:comment)
|
||||
|
||||
announce_data =
|
||||
File.read!("test/fixtures/mastodon-announce.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("object", comment.url)
|
||||
|
||||
{:ok, _, _} = Transmogrifier.handle_incoming(announce_data)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("id", comment.url)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert Discussions.get_comment_from_url(comment.url)
|
||||
end
|
||||
|
||||
setup :set_mox_from_context
|
||||
|
||||
test "it works for incoming actor deletes" do
|
||||
%Actor{url: url} =
|
||||
actor = insert(:actor, url: "https://framapiaf.org/users/admin", domain: "framapiaf.org")
|
||||
|
||||
%Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor)
|
||||
insert(:event, organizer_actor: actor)
|
||||
|
||||
%Comment{url: comment1_url} = comment1 = insert(:comment, actor: actor)
|
||||
insert(:comment, actor: actor)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 410, body: "Gone"}}
|
||||
end)
|
||||
|
||||
{:ok, _activity, _actor} = Transmogrifier.handle_incoming(data)
|
||||
assert %{success: 1, failure: 0} == Oban.drain_queue(queue: :background)
|
||||
|
||||
assert {:error, :actor_not_found} = Actors.get_actor_by_url(url)
|
||||
assert {:error, :event_not_found} = Events.get_event(event1.id)
|
||||
# Tombstone are cascade deleted, seems correct for now
|
||||
# assert %Tombstone{} = Tombstone.find_tombstone(event1_url)
|
||||
assert %Comment{deleted_at: deleted_at} = Discussions.get_comment(comment1.id)
|
||||
refute is_nil(deleted_at)
|
||||
# assert %Tombstone{} = Tombstone.find_tombstone(comment1_url)
|
||||
end
|
||||
|
||||
test "it fails for incoming actor deletes with spoofed origin" do
|
||||
%{url: url} = insert(:actor)
|
||||
deleted_actor_url = "https://framapiaf.org/users/admin"
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("actor", url)
|
||||
|
||||
deleted_actor_data =
|
||||
File.read!("test/fixtures/mastodon-actor.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("id", deleted_actor_url)
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{url: ^deleted_actor_url}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 200, body: deleted_actor_data}}
|
||||
end)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert :error == Transmogrifier.handle_incoming(data)
|
||||
end) =~ "Object origin check failed"
|
||||
|
||||
assert Actors.get_actor_by_url(url)
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle incoming delete activities for group posts" do
|
||||
test "works for remote deletions by moderators" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
remote_actor =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "https://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
insert(:member, actor: remote_actor, parent: group, role: :moderator)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
delete_data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
delete_data =
|
||||
delete_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, _activity, _actor} = Transmogrifier.handle_incoming(delete_data)
|
||||
|
||||
assert is_nil(Posts.get_post_by_url(data["id"]))
|
||||
end
|
||||
|
||||
test "doesn't work for remote deletions if the actor is just a group member" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
remote_actor =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "https://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
insert(:member, actor: remote_actor, parent: group, role: :member)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
delete_data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
delete_data =
|
||||
delete_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(delete_data)
|
||||
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
end
|
||||
|
||||
test "doesn't work for remote deletions if the actor is not a group member" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "https://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
delete_data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
delete_data =
|
||||
delete_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(delete_data)
|
||||
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle incoming delete activities for resources" do
|
||||
test "works for remote deletions" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
remote_actor =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "http://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
insert(:member, actor: remote_actor, parent: group, role: :member)
|
||||
%Resource{} = resource = insert(:resource, actor: group)
|
||||
|
||||
data = Convertible.model_to_as(resource)
|
||||
refute is_nil(Resources.get_resource_by_url(data["id"]))
|
||||
|
||||
delete_data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("type", "Document")
|
||||
|
||||
delete_data =
|
||||
delete_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, _activity, _actor} = Transmogrifier.handle_incoming(delete_data)
|
||||
|
||||
assert is_nil(Resources.get_resource_by_url(data["id"]))
|
||||
end
|
||||
|
||||
test "doesn't work for remote deletions if the actor is not a group member" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "http://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
delete_data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
delete_data =
|
||||
delete_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(delete_data)
|
||||
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
end
|
||||
end
|
||||
end
|
227
test/federation/activity_pub/transmogrifier/update_test.exs
Normal file
227
test/federation/activity_pub/transmogrifier/update_test.exs
Normal file
@ -0,0 +1,227 @@
|
||||
defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.UpdateTest do
|
||||
use Mobilizon.DataCase
|
||||
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
|
||||
use Oban.Testing, repo: Mobilizon.Storage.Repo
|
||||
import Mobilizon.Factory
|
||||
|
||||
alias Mobilizon.{Actors, Events, Posts}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Events.Event
|
||||
alias Mobilizon.Posts.Post
|
||||
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
|
||||
alias Mobilizon.Federation.ActivityStream.Convertible
|
||||
|
||||
describe "handle incoming update activities" do
|
||||
test "it works for incoming update activities on actors" do
|
||||
use_cassette "activity_pub/update_actor_activity" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
update_data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("id", data["actor"])
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: _data, local: false}, _} =
|
||||
Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(update_data["actor"])
|
||||
assert actor.name == "nextsoft"
|
||||
|
||||
assert actor.summary == "<p>Some bio</p>"
|
||||
end
|
||||
end
|
||||
|
||||
test "it works for incoming update activities on events" do
|
||||
use_cassette "activity_pub/event_update_activities" do
|
||||
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, %Event{id: event_id}} =
|
||||
Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert_enqueued(
|
||||
worker: Mobilizon.Service.Workers.BuildSearch,
|
||||
args: %{event_id: event_id, op: :insert_search_event}
|
||||
)
|
||||
|
||||
assert %{success: 1, failure: 0} == Oban.drain_queue(queue: :search)
|
||||
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("name", "My updated event")
|
||||
|> Map.put("id", data["object"]["id"])
|
||||
|> Map.put("type", "Event")
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, _} =
|
||||
Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
%Event{} = event = Events.get_event_by_url(data["object"]["id"])
|
||||
|
||||
assert_enqueued(
|
||||
worker: Mobilizon.Service.Workers.BuildSearch,
|
||||
args: %{event_id: event_id, op: :update_search_event}
|
||||
)
|
||||
|
||||
assert %{success: 1, failure: 0} == Oban.drain_queue(queue: :search)
|
||||
|
||||
assert event.title == "My updated event"
|
||||
|
||||
assert event.description == data["object"]["content"]
|
||||
end
|
||||
end
|
||||
|
||||
# test "it works for incoming update activities which lock the account" do
|
||||
# data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
# {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
# update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
# object =
|
||||
# update_data["object"]
|
||||
# |> Map.put("actor", data["actor"])
|
||||
# |> Map.put("id", data["actor"])
|
||||
# |> Map.put("manuallyApprovesFollowers", true)
|
||||
|
||||
# update_data =
|
||||
# update_data
|
||||
# |> Map.put("actor", data["actor"])
|
||||
# |> Map.put("object", object)
|
||||
|
||||
# {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
# user = User.get_cached_by_ap_id(data["actor"])
|
||||
# assert user.info["locked"] == true
|
||||
# end
|
||||
end
|
||||
|
||||
describe "handle incoming updates activities for group posts" do
|
||||
test "it works for incoming update activities on group posts when remote actor is a moderator" do
|
||||
use_cassette "activity_pub/group_post_update_activities" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
remote_actor =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "https://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
%Member{} = member = insert(:member, actor: remote_actor, parent: group, role: :moderator)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("name", "My updated post")
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, _} =
|
||||
Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
%Post{id: updated_post_id, title: updated_post_title} =
|
||||
Posts.get_post_by_url(data["object"]["id"])
|
||||
|
||||
assert updated_post_id == post.id
|
||||
assert updated_post_title == "My updated post"
|
||||
end
|
||||
end
|
||||
|
||||
test "it works for incoming update activities on group posts" do
|
||||
use_cassette "activity_pub/group_post_update_activities" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
remote_actor =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "https://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
%Member{} = member = insert(:member, actor: remote_actor, parent: group)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("name", "My updated post")
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
%Post{id: updated_post_id, title: updated_post_title} = Posts.get_post_by_url(data["id"])
|
||||
|
||||
assert updated_post_id == post.id
|
||||
refute updated_post_title == "My updated post"
|
||||
end
|
||||
end
|
||||
|
||||
test "it fails for incoming update activities on group posts when the actor is not a member from the group" do
|
||||
use_cassette "activity_pub/group_post_update_activities" do
|
||||
%Actor{url: remote_actor_url} =
|
||||
insert(:actor,
|
||||
domain: "remote.domain",
|
||||
url: "https://remote.domain/@remote",
|
||||
preferred_username: "remote"
|
||||
)
|
||||
|
||||
group = insert(:group)
|
||||
%Post{} = post = insert(:post, attributed_to: group)
|
||||
|
||||
data = Convertible.model_to_as(post)
|
||||
refute is_nil(Posts.get_post_by_url(data["id"]))
|
||||
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
data
|
||||
|> Map.put("name", "My updated post")
|
||||
|> Map.put("type", "Article")
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", remote_actor_url)
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
%Post{id: updated_post_id, title: updated_post_title} = Posts.get_post_by_url(data["id"])
|
||||
|
||||
assert updated_post_id == post.id
|
||||
refute updated_post_title == "My updated post"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -10,11 +10,10 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|
||||
use Oban.Testing, repo: Mobilizon.Storage.Repo
|
||||
|
||||
import Mobilizon.Factory
|
||||
import ExUnit.CaptureLog
|
||||
import Mock
|
||||
import Mox
|
||||
|
||||
alias Mobilizon.{Actors, Discussions, Events}
|
||||
alias Mobilizon.{Actors, Discussions}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Discussions.Comment
|
||||
alias Mobilizon.Events.Event
|
||||
@ -707,233 +706,6 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle incoming update activities" do
|
||||
test "it works for incoming update activities on actors" do
|
||||
use_cassette "activity_pub/update_actor_activity" do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, _} = Transmogrifier.handle_incoming(data)
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
update_data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("id", data["actor"])
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: _data, local: false}, _} =
|
||||
Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
{:ok, %Actor{} = actor} = Actors.get_actor_by_url(update_data["actor"])
|
||||
assert actor.name == "nextsoft"
|
||||
|
||||
assert actor.summary == "<p>Some bio</p>"
|
||||
end
|
||||
end
|
||||
|
||||
test "it works for incoming update activities on events" do
|
||||
use_cassette "activity_pub/event_update_activities" do
|
||||
data = File.read!("test/fixtures/mobilizon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, %Event{id: event_id}} =
|
||||
Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert_enqueued(
|
||||
worker: Mobilizon.Service.Workers.BuildSearch,
|
||||
args: %{event_id: event_id, op: :insert_search_event}
|
||||
)
|
||||
|
||||
assert %{success: 1, failure: 0} == Oban.drain_queue(queue: :search)
|
||||
|
||||
update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("name", "My updated event")
|
||||
|> Map.put("id", data["object"]["id"])
|
||||
|> Map.put("type", "Event")
|
||||
|
||||
update_data =
|
||||
update_data
|
||||
|> Map.put("actor", data["actor"])
|
||||
|> Map.put("object", object)
|
||||
|
||||
{:ok, %Activity{data: data, local: false}, _} =
|
||||
Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
%Event{} = event = Events.get_event_by_url(data["object"]["id"])
|
||||
|
||||
assert_enqueued(
|
||||
worker: Mobilizon.Service.Workers.BuildSearch,
|
||||
args: %{event_id: event_id, op: :update_search_event}
|
||||
)
|
||||
|
||||
assert %{success: 1, failure: 0} == Oban.drain_queue(queue: :search)
|
||||
|
||||
assert event.title == "My updated event"
|
||||
|
||||
assert event.description == data["object"]["content"]
|
||||
end
|
||||
end
|
||||
|
||||
# test "it works for incoming update activities which lock the account" do
|
||||
# data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
|
||||
# {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
# update_data = File.read!("test/fixtures/mastodon-update.json") |> Jason.decode!()
|
||||
|
||||
# object =
|
||||
# update_data["object"]
|
||||
# |> Map.put("actor", data["actor"])
|
||||
# |> Map.put("id", data["actor"])
|
||||
# |> Map.put("manuallyApprovesFollowers", true)
|
||||
|
||||
# update_data =
|
||||
# update_data
|
||||
# |> Map.put("actor", data["actor"])
|
||||
# |> Map.put("object", object)
|
||||
|
||||
# {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)
|
||||
|
||||
# user = User.get_cached_by_ap_id(data["actor"])
|
||||
# assert user.info["locked"] == true
|
||||
# end
|
||||
end
|
||||
|
||||
describe "handle incoming delete activities" do
|
||||
test "it works for incoming deletes" do
|
||||
%Actor{url: actor_url} =
|
||||
actor = insert(:actor, url: "http://mobilizon.tld/@remote", domain: "mobilizon.tld")
|
||||
|
||||
%Comment{url: comment_url} =
|
||||
insert(:comment,
|
||||
actor: nil,
|
||||
actor_id: actor.id,
|
||||
url: "http://mobilizon.tld/comments/9f3794b8-11a0-4a98-8cb7-475ab332c701"
|
||||
)
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "http://mobilizon.tld/comments/9f3794b8-11a0-4a98-8cb7-475ab332c701"},
|
||||
_opts ->
|
||||
{:ok, %Tesla.Env{status: 410, body: "Gone"}}
|
||||
end)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("id", comment_url)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("actor", actor_url)
|
||||
|
||||
assert Discussions.get_comment_from_url(comment_url)
|
||||
assert is_nil(Discussions.get_comment_from_url(comment_url).deleted_at)
|
||||
|
||||
{:ok, %Activity{local: false}, _} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
refute is_nil(Discussions.get_comment_from_url(comment_url).deleted_at)
|
||||
end
|
||||
|
||||
test "it fails for incoming deletes with spoofed origin" do
|
||||
comment = insert(:comment)
|
||||
|
||||
announce_data =
|
||||
File.read!("test/fixtures/mastodon-announce.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("object", comment.url)
|
||||
|
||||
{:ok, _, _} = Transmogrifier.handle_incoming(announce_data)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("id", comment.url)
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert Discussions.get_comment_from_url(comment.url)
|
||||
end
|
||||
|
||||
setup :set_mox_from_context
|
||||
|
||||
test "it works for incoming actor deletes" do
|
||||
%Actor{url: url} =
|
||||
actor = insert(:actor, url: "https://framapiaf.org/users/admin", domain: "framapiaf.org")
|
||||
|
||||
%Event{url: event1_url} = event1 = insert(:event, organizer_actor: actor)
|
||||
insert(:event, organizer_actor: actor)
|
||||
|
||||
%Comment{url: comment1_url} = comment1 = insert(:comment, actor: actor)
|
||||
insert(:comment, actor: actor)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|> Jason.decode!()
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{method: :get, url: "https://framapiaf.org/users/admin"}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 410, body: "Gone"}}
|
||||
end)
|
||||
|
||||
{:ok, _activity, _actor} = Transmogrifier.handle_incoming(data)
|
||||
assert %{success: 1, failure: 0} == Oban.drain_queue(queue: :background)
|
||||
|
||||
assert {:error, :actor_not_found} = Actors.get_actor_by_url(url)
|
||||
assert {:error, :event_not_found} = Events.get_event(event1.id)
|
||||
# Tombstone are cascade deleted, seems correct for now
|
||||
# assert %Tombstone{} = Tombstone.find_tombstone(event1_url)
|
||||
assert %Comment{deleted_at: deleted_at} = Discussions.get_comment(comment1.id)
|
||||
refute is_nil(deleted_at)
|
||||
# assert %Tombstone{} = Tombstone.find_tombstone(comment1_url)
|
||||
end
|
||||
|
||||
test "it fails for incoming actor deletes with spoofed origin" do
|
||||
%{url: url} = insert(:actor)
|
||||
deleted_actor_url = "https://framapiaf.org/users/admin"
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("actor", url)
|
||||
|
||||
deleted_actor_data =
|
||||
File.read!("test/fixtures/mastodon-actor.json")
|
||||
|> Jason.decode!()
|
||||
|> Map.put("id", deleted_actor_url)
|
||||
|
||||
Mock
|
||||
|> expect(:call, fn
|
||||
%{url: ^deleted_actor_url}, _opts ->
|
||||
{:ok, %Tesla.Env{status: 200, body: deleted_actor_data}}
|
||||
end)
|
||||
|
||||
assert capture_log(fn ->
|
||||
assert :error == Transmogrifier.handle_incoming(data)
|
||||
end) =~ "Object origin check failed"
|
||||
|
||||
assert Actors.get_actor_by_url(url)
|
||||
end
|
||||
end
|
||||
|
||||
describe "handle tombstones" do
|
||||
setup :verify_on_exit!
|
||||
|
||||
|
24
test/fixtures/vcr_cassettes/activity_pub/group_post_update_activities.json
vendored
Normal file
24
test/fixtures/vcr_cassettes/activity_pub/group_post_update_activities.json
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"request": {
|
||||
"body": "{\"@context\":[\"https://www.w3.org/ns/activitystreams\",\"https://litepub.social/litepub/context.jsonld\",{\"Hashtag\":\"as:Hashtag\",\"PostalAddress\":\"sc:PostalAddress\",\"address\":{\"@id\":\"sc:address\",\"@type\":\"sc:PostalAddress\"},\"addressCountry\":\"sc:addressCountry\",\"addressLocality\":\"sc:addressLocality\",\"addressRegion\":\"sc:addressRegion\",\"anonymousParticipationEnabled\":{\"@id\":\"mz:anonymousParticipationEnabled\",\"@type\":\"sc:Boolean\"},\"category\":\"sc:category\",\"commentsEnabled\":{\"@id\":\"pt:commentsEnabled\",\"@type\":\"sc:Boolean\"},\"discoverable\":\"toot:discoverable\",\"ical\":\"http://www.w3.org/2002/12/cal/ical#\",\"joinMode\":{\"@id\":\"mz:joinMode\",\"@type\":\"mz:joinModeType\"},\"joinModeType\":{\"@id\":\"mz:joinModeType\",\"@type\":\"rdfs:Class\"},\"location\":{\"@id\":\"sc:location\",\"@type\":\"sc:Place\"},\"manuallyApprovesFollowers\":\"as:manuallyApprovesFollowers\",\"maximumAttendeeCapacity\":\"sc:maximumAttendeeCapacity\",\"mz\":\"https://joinmobilizon.org/ns#\",\"participationMessage\":{\"@id\":\"mz:participationMessage\",\"@type\":\"sc:Text\"},\"postalCode\":\"sc:postalCode\",\"pt\":\"https://joinpeertube.org/ns#\",\"repliesModerationOption\":{\"@id\":\"mz:repliesModerationOption\",\"@type\":\"mz:repliesModerationOptionType\"},\"repliesModerationOptionType\":{\"@id\":\"mz:repliesModerationOptionType\",\"@type\":\"rdfs:Class\"},\"sc\":\"http://schema.org#\",\"streetAddress\":\"sc:streetAddress\",\"toot\":\"http://joinmastodon.org/ns#\",\"uuid\":\"sc:identifier\"}],\"actor\":\"http://mobilizon.test/@myGroup0\",\"cc\":[],\"id\":\"http://mobilizon.test/announces/839e0ffc-f437-48db-afba-9ce1e971e938\",\"object\":{\"actor\":\"http://mobilizon.test/@thomas0\",\"attributedTo\":\"http://mobilizon.test/@myGroup0\",\"content\":\"The <b>HTML</b>body for my Article\",\"id\":\"http://mobilizon.test/p/6a482d5f-94fc-446b-84bb-d4d386d5dd45\",\"name\":\"My updated post\",\"published\":\"2020-10-19T08:37:52Z\",\"type\":\"Article\"},\"to\":[\"http://mobilizon.test/@myGroup0/members\"],\"type\":\"Announce\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/activity+json",
|
||||
"signature": "keyId=\"http://mobilizon.test/@myGroup0#main-key\",algorithm=\"rsa-sha256\",headers=\"(request-target) content-length date digest host\",signature=\"P+7rSSUeUBdX74wbvSEe4roG7yh7MfpF6s4tjv5q1kbeVKtXZRyfC1LqgVNCADZYXFqYlMvfF7DiaRQRiMznGWawM/QXK08eXiAVihYK28Pa56BfI68OUakd+FptlwfB4WJ4Jc7xi1z+iarv+EvlFxjkG5pgwL4mW49rvNnigELzypGtp2bj/2BhiBItHutvOju1MwLR1EBQFJBSZDVZZKbHTcV4KbGtbYvkWUbH8fZbe3fgctKlvO/z9kw+yBTTIEE1O18F4HiJ17nYtaaxv3/vl5RxcjYLpf+QQzkaPOsSLZs8zpIZZp3BbLtPh+OGwkyK9PBQsaI0N1ZSLQ5gaQ==\"",
|
||||
"digest": "SHA-256=EyZ+uZ/Vv2lUK8ozgOHBpnoUWUM5WQHATQb1tEMldNU=",
|
||||
"date": "Mon, 19 Oct 2020 08:37:52 GMT"
|
||||
},
|
||||
"method": "post",
|
||||
"options": [],
|
||||
"request_body": "",
|
||||
"url": "http://mobilizon.test/inbox"
|
||||
},
|
||||
"response": {
|
||||
"binary": false,
|
||||
"body": "nxdomain",
|
||||
"headers": [],
|
||||
"status_code": null,
|
||||
"type": "error"
|
||||
}
|
||||
}
|
||||
]
|
@ -341,7 +341,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ResourceTest do
|
||||
}
|
||||
)
|
||||
|
||||
assert hd(res["errors"])["message"] == "No such resource"
|
||||
assert hd(res["errors"])["message"] == "Resource not found"
|
||||
end
|
||||
|
||||
test "get_resource/3 for a non-existing group", %{
|
||||
@ -778,7 +778,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ResourceTest do
|
||||
}
|
||||
)
|
||||
|
||||
assert hd(res["errors"])["message"] == "No such resource"
|
||||
assert hd(res["errors"])["message"] == "Resource not found"
|
||||
end
|
||||
|
||||
test "delete_resource/3 deletes a folder and children", %{
|
||||
@ -828,7 +828,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ResourceTest do
|
||||
}
|
||||
)
|
||||
|
||||
assert hd(res["errors"])["message"] == "No such resource"
|
||||
assert hd(res["errors"])["message"] == "Resource not found"
|
||||
|
||||
res =
|
||||
conn
|
||||
@ -841,7 +841,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ResourceTest do
|
||||
}
|
||||
)
|
||||
|
||||
assert hd(res["errors"])["message"] == "No such resource"
|
||||
assert hd(res["errors"])["message"] == "Resource not found"
|
||||
end
|
||||
|
||||
test "delete_resource/3 deletes a resource not found", %{
|
||||
|
Loading…
Reference in New Issue
Block a user